Улучшение диагностики с помощью static_assert

В шаблонном программировании static_assert помогает программистам проверять ограничения на аргументы шаблона и генерировать человек читаемый сообщения об ошибках при нарушении ограничений.

Рассмотрим этот код,

template<typename T>
void f(T)
{
static_assert(T(), "first requirement failed to meet.");

static_assert(T::value, "second requirement failed to meet.");

T t = 10; //even this may generate error!
}

Моя мысль: если первый static_assert не получается, значит немного требование к T не встречается, следовательно, компиляция должна прекратиться, генерируя только первый сообщение об ошибке — потому что бессмысленно продолжать компиляцию, просто чтобы генерировать все больше и больше сообщений об ошибках, большинство из которых довольно часто указать на не замужем нарушение ограничений. Сотни сообщений об ошибках, вместо одного, выглядят очень страшно на экране — я бы даже сказал, это бросает вызов самому цель из static_assert в некоторой степени.

Например, если я вызову шаблон функции выше, как:

f(std::false_type{});

GCC 4.8 генерирует следующее:

main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:16:24:   required from here
main.cpp:7:5: error: static assertion failed: first requirement failed to meet.
static_assert(T(), "first requirement failed to meet.");
^
main.cpp:9:5: error: static assertion failed: second requirement failed to meet.
static_assert(T::value, "second requirement failed to meet.");
^
main.cpp:11:11: error: conversion from 'int' to non-scalar type 'std::integral_constant<bool, false>' requested
T t = 10; //even this may generate error!

Как вы видете (онлайн), это слишком много ошибок. Если первый static_assert сбои, очень вероятно, что остальная часть кода будет также потерпеть неудачу если компиляция продолжается, зачем продолжать компиляцию? Я уверен, что в программировании шаблонов многие программисты не хотят таких каскадных сообщений об ошибках!

Я пытался решить эту проблему путем расщепление функция на несколько функций, в каждой проверяется только одно ограничение, как:

template<typename T>
void f_impl(T); //forward declaration

template<typename T>
void f(T)
{
static_assert(T(), "first requirement failed to meet.");
f_impl(T());
}

template<typename T>
void f_impl(T)
{
static_assert(T::value, "second requirement failed to meet.");
T t = 10;
}

f(std::false_type{}); //call

Теперь это создает это:

main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:24:24:   required from here
main.cpp:10:5: error: static assertion failed: first requirement failed to meet.
static_assert(T(), "first requirement failed to meet.");
^

Это большое улучшение — только одно сообщение об ошибке намного легче читать и понимать (см. онлайн).

Мой вопрос

  • Почему компиляция не останавливается на первом static_assert?
  • Поскольку разбиение шаблона функции и проверка одного ограничения в каждом function_impl, помогает только GCC и лязг еще генерирует много ошибок, Есть ли способ улучшить диагностику более согласованным способом — то, что работает для всех компиляторов?

9

Решение

Я согласен с Дэвидом Родригесом — dribeas и в защиту авторов компиляторов рассмотрим этот пример:

#include <type_traits>

class A {};

// I want the nice error message below in several functions.
// Instead of repeating myself, let's put it in a function.
template <typename U>
void check() {
static_assert(std::is_convertible<U*, const volatile A*>::value,
"U doesn't derive publicly from A ""(did you forget to include it's header file?)");
}

template <typename U>
void f(U* u) {
// check legality (with a nice error message)
check<U>();
// before trying a failing initialization:
A* p = u;
}

class B; // I forget to include "B.h"
int main() {
B* b = nullptr;
f(b);
}

Когда экземпляр f<B> запускает компилятор (или автор компилятора) может подумать: «Хм … Мне нужно создать экземпляр check<U> и люди всегда жалуются, что компиляция шаблонов идет слишком медленно. Так что я продолжу, и, возможно, что-то не так внизу, и мне не нужно создавать экземпляры check«.

Я считаю, что рассуждения выше имеют смысл. (Обратите внимание, что я не автор компилятора, так что я просто размышляю здесь).

И GCC 4.8, и VS2010 продолжают компилировать f<B>, откладывая создание check<B> Для последующего. Затем они находят неудачную инициализацию и предоставляют свои собственные сообщения об ошибках. VS2010 немедленно останавливается, и я не получаю свое хорошее сообщение об ошибке! GCC продолжает идти и выдает сообщение, которое я хотел (но только после своего).

Метапрограммирование сложно для программистов и для компиляторов. static_assert помогает много но это не панацея.

4

Другие решения

Есть несколько целей, которые должны быть сбалансированы здесь. В частности, более простые сообщения об ошибках меньшего размера могут быть получены путем остановки первой ошибки, что хорошо. В то же время остановка на первой ошибке не дает вам информации о любых других проблемах, которые вы, возможно, захотите решить, прежде чем пытаться выполнить другую потенциально дорогостоящую компиляцию. Например, в вашем первом примере я лично предпочитаю все static_assertы должны быть проверены сразу. Прочитайте сообщение об ошибке как:

Вы не выполнили следующие требования:

  • конструктор по умолчанию
  • вложенными value тип

Я предпочел бы, чтобы обе ошибки были обнаружены на первом проходе, чем исправить одну, и мне потребуется несколько минут, чтобы система сборки отключилась на следующем.

Предполагается, что компилятор может восстановиться после ошибки и продолжить синтаксический анализ, хотя грамматика зависит от контекста, и это не всегда так, поэтому часть негативной стороны проблемы заключается в том, что вы можете доверять первой ошибке, но Следующие ошибки могут быть следствием первой ошибки, и для того, чтобы понять, что есть что, нужен опыт.

Все это — качество реализации (следовательно, зависит от компилятора), и многие реализации позволяют вам определить, когда остановиться, так что это зависит от пользователя и флагов, которые передаются компилятору. Компиляторы получают лучшие отчеты об ошибках и исправляются после них, так что вы можете ожидать улучшения здесь. Для дальнейшего улучшения> C ++ 14 (C ++ 17? Позже?) Добавит концепции, предназначенные для улучшения сообщений об ошибках.

Подводя итог:

  • Это качество реализации и может контролироваться с помощью флагов компилятора.
  • Не каждый хочет того, что вы хотите, некоторые хотят обнаруживать более одной ошибки за каждый проход компилятора
  • Будущее придет с лучшими сообщениями об ошибках (концепции, улучшения компилятора)
4

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector