Интегрировать имя типа в static_assert output?
Мне нравится давать полезные ошибки/сообщения, и я также хочу сделать это для моих static_assert
s. Проблема в том, что они зависят от параметров шаблона. Обычно эти параметры будут отображаться так или иначе из-за возникшей ошибки, но они либо неясны, либо не сгруппированы, поэтому они имеют смысл. Пример:
template<class T>
struct fake_dependency{
static bool const value = false;
};
template<class T, class Tag>
struct Foo{
Foo(){}
template<class OtherTag>
Foo(Foo<T, OtherTag> const&){
static_assert(fake_dependency<T>::value, "Cannot create Foo<T,Tag> from Foo<T,OtherTag>.");
}
};
int main(){
Foo<int, struct TagA> fA;
Foo<int, struct TagB> fB(fA);
}
Выход на MSVC:
src\main.cpp(74): error C2338: Cannot create Foo<T,Tag> from Foo<T,OtherTag>.
src\main.cpp(84) : see reference to function template instantiation 'Foo<T,Tag>::Foo<main::TagA>(const Foo<T,main::TagA> &)' being compiled
with
[
T=int,
Tag=main::TagB
]
Один тег упоминается в самом шаблоне функции, а другой ниже - с шаблоном класса. Не очень приятно. Давайте посмотрим, что Выходы GCC:
prog.cpp: In constructor 'Foo<T, Tag>::Foo(const Foo<T, OtherTag>&) [with OtherTag = main()::TagA, T = int, Tag = main()::TagB]':
prog.cpp:18:32: instantiated from here
prog.cpp:12:5: error: static assertion failed: "Cannot create Foo<T,Tag> from Foo<T,OtherTag>."
Гораздо лучше, но все еще не так, где находится static_assert
. А теперь представьте еще несколько параметров, или больше шаблонов, или и то, и другое. мурашки
Один из способов обойти это - использовать промежуточную структуру, которая принимает оба тега в качестве параметров шаблона:
template<class Tag, class OtherTag>
struct static_Foo_assert{
static_assert(fake_dependency<Tag>::value, "Cannot create Foo<T,Tag> from Foo<T,OtherTag>.");
};
template<class T, class Tag>
struct Foo{
Foo(){}
template<class OtherTag>
Foo(Foo<T, OtherTag> const&){
static_Foo_assert<Tag, OtherTag> x;
}
};
Теперь снова посмотрим на выход:
src\main.cpp(70): error C2338: Cannot create Foo<T,Tag> from Foo<T,OtherTag>.
src\main.cpp(79) : see reference to class template instantiation 'static_Foo_assert<Tag,OtherTag>' being compiled
with
[
Tag=main::TagB,
OtherTag=main::TagA
]
Гораздо лучше! Здесь GCC говорит:
prog.cpp: In instantiation of 'static_Foo_assert<main()::TagB, main()::TagA>':
prog.cpp:17:40: instantiated from 'Foo<T, Tag>::Foo(const Foo<T, OtherTag>&) [with OtherTag = main()::TagA, T = int, Tag = main()::TagB]'
prog.cpp:23:32: instantiated from here
prog.cpp:8:5: error: static assertion failed: "Cannot create Foo<T,Tag> from Foo<T,OtherTag>."
Выглядит неплохо. Проблема: мне нужно создать такую структуру для каждого шаблона, так как сообщение об ошибке в static_assert
должно быть строковым литералом...
Теперь, по моему вопросу: можем ли мы каким-то образом включить имена типов непосредственно в static_assert
? Как
static_assert(..., "Cannot create Foo<" T "," Tag "> from Foo<" T "," OtherTag ">.");
Пример вывода:
Невозможно создать Foo<int,main::TagA>
из Foo<int,main::TagB>
.
Или, если это невозможно, можем ли мы каким-то образом сделать сообщение об ошибке дополнительным параметром шаблона, чтобы сделать его доступным?
Ответы
Ответ 1
Мой взлом
Код:
template <typename Assertion>
struct AssertValue : AssertionChecker<Assertion::value, Assertion>
{
static_assert(AssertionValue, "Assertion failed <see below for more information>");
static bool const value = Assertion::value;
};
Он позволяет проверить любое утверждение ::value
и сбросить типы, если это не удалось.
Использование:
// Bad indentation used to show parts
static_assert(
AssertValue<
std::my_check<
T0, decltype(*somethingComplicated), T7::value_type
>
>,
"something horrible happened"
);
где std::my_check<...>::value
- логический результат проверки
Пример
Для полного примера SSCCE см.: Пример IDEOne
Пример сообщения об ошибке:
prog.cpp: In instantiation of 'AssertValue<std::is_base_of<IMyInterface, MyBadType> >':
prog.cpp:37:69: instantiated from 'void MyFunction(IteratorType, IteratorType) [with IteratorType = __gnu_cxx::__normal_iterator<MyBadType*, std::vector<MyBadType> >]'
prog.cpp:60:38: instantiated from here
prog.cpp:9:5: error: static assertion failed: "Assertion failed <see below for more information>"
prog.cpp: In function 'void MyFunction(IteratorType, IteratorType) [with IteratorType = __gnu_cxx::__normal_iterator<MyBadType*, std::vector<MyBadType> >]':
prog.cpp:60:38: instantiated from here
prog.cpp:39:5: error: static assertion failed: "iterator passed does not reference IMyInterface items"
Объяснение
Если утверждение не удастся, оно напечатает аргументы шаблона AssertValue и, следовательно, распечатает полное расширение шаблона вашей проверки. Например, если вы проверили std::is_base_of
, он напечатает полный тип проверки, например: std::is_base_of<IMyInterface, MyBadType>
. Затем вы точно знаете, какие типы были использованы в неудавшемся утверждении.
Единственная проблема заключается в том, что это работает только с шаблонами, которые ставят их результат в ::value
. Однако type_traits
в основном использует это и является стандартом goto.
Ответ 2
Можно получить строковый литерал, переданный как шаблонный непиковый параметр, с немного hoop-jumping. Но так как второй аргумент static_assert
ограничивается строковым литералом, а не выражением адреса, то, к сожалению, это не очень полезно.
К сожалению, я подозреваю, что ваш лучший выбор - лоббировать комитет или авторов компилятора, чтобы расширить объект.
Ответ 3
Если ваш компилятор предоставляет макрос __FUNCTION__
, вы можете иметь действительно простую замену, используя его и конкатенацию литерала. Однако реализация gcc и clang не выполняется как макрос, поэтому это решение не будет работать для них.
#include "stdafx.h"
#include <type_traits>
template <class T>
class must_be_pod
{
static void test() { static_assert (std::is_pod<T>::value, __FUNCTION__ ": not a POD"); }
public:
must_be_pod() { test(); }
};
class not_a_pod
{
public:
not_a_pod() {}
virtual ~not_a_pod() {}
};
int main()
{
must_be_pod<not_a_pod> should_fail; // and it does
return 0;
}
Это создает следующий результат при компиляции VS2015:
static_assert_test.cpp(10): error C2338: must_be_pod<class not_a_pod>::test: not a POD
Ответ 4
std::type_info
имеет член const char* name()
:
#include <typeinfo>
using namespace std;
//...
const char* name = type_info(T).name();