Integrate type name in static_assert output?

I like to give helpful errors / messages, and I also want to do so for my static_asserts. The problem is, that they depend on template parameters. Normally, those parameters will get displayed on way or an other due to the error raised, but they are either obscure or not grouped so they make sense. Example:

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);
}

Output on 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
          ]

One tag is mentioned in the function template itself, the other below with the class template. Not so nice. Lets see what GCC outputs:

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>."

Much better, but still not really where the static_assert is. And now imagine some more parameters, or more templates, or both. shivers

One way to work around that is to use an intermediate struct, which takes both Tags as template parameters:

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;
  }
};

Now lets see the output again:

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
          ]

Much better! Here's what GCC says:

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>."

Looks not bad. The problem: I need to create such a struct for every template, since the error message in static_assert needs to be a string literal...

Now, for my question: Can we somehow include the type names directly into the static_assert? Like

static_assert(..., "Cannot create Foo<" T "," Tag "> from Foo<" T "," OtherTag ">.");

Example output:

Cannot create Foo<int,main::TagA> from Foo<int,main::TagB>.

Or, if that isn't achievable, can we somehow make the error message an extra template parameter, as to make it passable?


Solution 1:

My Hack

Code:

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;
};

It allows for you to check any ::value assertion and dump the types if it failed.

Usage:

// Bad indentation used to show parts
static_assert(
    AssertValue<
        std::my_check<
            T0, decltype(*somethingComplicated), T7::value_type
        >
    >, 
    "something horrible happened"
);

where std::my_check<...>::value is the boolean result of the check

Example

For a full SSCCE example see: IDEOne Example

The Example's error message:

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"

Explanation

If the assertion fails, it will print the template arguments of AssertValue and therefore print the full template expansion of your check. For example, if you were checking a std::is_base_of it will print the full type of the check, e.g.: std::is_base_of<IMyInterface, MyBadType>. Then you know exactly what types were used in the failed assertion.

The only problem is that this only works on templates that put their result in ::value. However type_traits mostly uses this and is the goto standard.

Solution 2:

It's possible to get a string literal passed in as a template non-type parameter, with a little bit of hoop-jumping. But since the second argument to static_assert is constrained to be a string literal rather than, say, an address constant expression, this unfortunately is not much use.

Sadly I suspect your best bet is to lobby the committee or the compiler writers to extend the facility.

Solution 3:

If your compiler provides the __FUNCTION__ macro, you can have a really simple substitution using it and literal concatenation. However, gcc's and clang's implementation is not done as a macro, so this solution will not work for them.

#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;
}

This produces the following output when compiled by VS2015:

static_assert_test.cpp(10): error C2338: must_be_pod<class not_a_pod>::test: not a POD