2013-12-02 3 views
9

В программировании шаблонов 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 не соответствует, следовательно, компиляция должна прекратить, генерируя только первый сообщение об ошибке —, потому что не имеет смысла продолжать компиляцию, чтобы генерировать все больше сообщений об ошибках, большинство из которых часто указывает на одиночный ограничение violati на. Сотни сообщений об ошибках, а не только один, выглядят очень страшно на экране — Я бы даже сказал, что это не совсем то, что цели от 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! 

Как вы можете видеть (online), то есть слишком много ошибок , Если первый 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."); 
    ^

То есть много улучшения — просто одно сообщение об ошибке намного легче читать и понимать (см. online).

Мой вопрос,

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

В GCC и Clang есть возможность остановиться после одной ошибки, но это не слишком хорошо работает из моего опыта, так как некоторые ошибки имеют жизненно важную информацию. Конечно, я не думаю, что это совсем то, что вам нужно: p – chris

+1

Эта опция -Wfatal-errors, и именно то, что я бы рекомендовал (кроме случаев, когда комментарий @ chris прав, и это исключает важную информацию). Я думаю, что компиляторы считают, что ответственность за среду разработки (возможно, IDE) проявляется в презентабельной форме. – hvd

+0

@hvd, Clang действительно отлично поработал, когда я ловил пример. Раньше я использовал его только с GCC. В любом случае, как один пример, посмотрите, как GCC отключает список кандидатов: http://coliru.stacked-crooked.com/a/4fdcf6e8bd878ffb – chris

ответ

4

Я согласен с Дэвидом Родригесом - 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> начинается компилятор (или writter компилятор) может подумать: «Хм ... Мне нужно создать экземпляр check<U> и люди всегда жалуются, что компиляция шаблонов слишком медленно. Так что я буду держать, и, возможно, что-то не ниже, и я не событие нужно создать экземпляр check

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

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

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

+0

Ох я видел это [происходящее] (http://coliru.stacked-crooked.com/a/a6187627a70527b6). GCC действительно пропускает экземпляр 'f_impl()' и все еще продолжает компиляцию текущей функции 'f()' дает ошибку на 'T t = 10;'. Теперь это начинает иметь смысл для меня. – Nawaz

4

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

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

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

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

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

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

Сведение:

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