C++11 Static_Assert and Template Instantiation

C++11 static_assert and template instantiation

GCC is correct and the other compiler is correct too. Refer to 14.6p8 in the spec

If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required.

Therefor, a compiler is free to reject the following

template<typename T>
void f() {
static_assert(0, "may trigger immediately!");
static_assert(sizeof(T) == 0, "may trigger immediately!");
}

If you want to go safe, you have to arrange it so the compiler cannot know until instantiation whether the boolean expression will be true or false. For example, get the value by getvalue<T>::value, with getvalue being a class template (one could specialize it, so the compiler cannot possibly know the boolean value already).

static_assert with partial template specialization

The expression in the static_assert must be dependent on a template parameter if you wish it to be instantiation-time only. This is guaranteed by Standard- the implementation may (but has no obligation to) check static_assertions in templates that are not dependent on any template parameter.

Your code is a strange roundabout way of doing something like

template<typename T> struct S {
static_assert(std::is_integral<T>::value, "type unsupported");
void foo() {}
};

This clearly communicates to the compiler the dependency between the expression and the template parameter, and is far clearer and easier to read as well. I actually couldn't quite figure out if you wanted compilation to fail for integral types or for non-integral types.

When is static_assert evaluated relative to template instantiation

There ain't no such thing as the order of evaluation of static_asserts relative to template instantiations. The standard simply says that, if the expression in the first parameter of static_assert evaluates to false at compile time, the program is ill-formed and the diagnostic should include the text from the second parameter.

In other words, static_assert(false) is treated the same as a syntax error or any other violation of well-formedness constraints. Naturally, if there are multiple errors that render a program ill-formed, the standard doesn't specify the order in which the compiler should issue diagnostic for them, nor even whether it should issue a message for each error.

What is the proper way to use static_assert in a non-instantiation template?

If the expression inside static_assert depends on a template parameter, the assertion will be deferred until instantiation. You could, for example, define the following class template, deferred_false:

#include <type_traits> // std::false_type

template<typename>
struct deferred_false: std::false_type {};

Then, use it in the static_assert:

template <typename T, typename = void>
struct A {
void Print() {
static_assert(deferred_false<T>::value, "fall into unsupported Print function");
}
};

Default template is matching despite static_assert

Why is foo(int) in this code ambiguous?

Because the version with static_assert() give error if selected but still exist; so the compiler doesn't know if choose the generic version or the integer enabled version.

Is there a better way to use templates to get this desired behavior (explicit bool specialization + implicit int specialization)?

A possible way is to avoid the generic version and SFINAE enable the version you need

The following is a full working example

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_same<T, bool>::value>::type foo(T val)
{ std::cout << "bool case " << val << std::endl; }

template <typename T>
typename std::enable_if< ! std::is_same<T, bool>::value
&& std::is_convertible<T, int>::value>::type foo(T val)
{ std::cout << "integer case " << (int)val << std::endl; }

int main()
{
foo(true); // bool case
foo(1); // integer case
foo(2U); // integer case
foo(3L); // integer case
foo(4UL); // integer case
foo(5LL); // integer case
foo(6ULL); // integer case

// foo((void*)nullptr); // compilation error
}

-- EDIT --

The OP

Sorry, I am still confused. Could you elaborate? I thought that due to SFINAE, that if an error occurred in substitution, it would use the other template.

Exactly.
The problem is when there isn't an error in substitution and the compiler have to choose between two different version of the same template.

I mean: in your example, when you call foo(5), there isn't error in substitution of

typename std::enable_if<!std::is_same<T,bool>::value
&& std::is_convertible<T,int>::value,int>::type=0>

So the compiler have to choose between the two template functions

template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}

//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}

that differ only for a template value with a default value, so are (from the compiler point of view) indistinguishable.

And observe that

template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}

is a (full) template specialization but

//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}

isn't a template specialization (no partial template specialization of a function is admitted in C++11/14/17; you can partial specialize only structs/classes); is a generic template.

static_assert not evaluated in template parameter

Operand of decltype is not evaluated and therefore does not require function definition to exist (or return type being complete) while function template is instantiated when the specialization is referenced in a context that requires a function definition to exist. So you need to instantiate function template for check inside of function body to take place:

template<typename T, auto Dummy = &has_foo_f<T>>
void bar(){}

static_assert and class templates

For B<A<1>> b;, A<1> is only used as template argument, which doesn't cause implicit instantiation of class template A, then the static_assert inside A's definition won't be triggered.

When code refers to a template in context that requires a completely defined type, or when the completeness of the type affects the code, and this particular type has not been explicitly instantiated, implicit instantiation occurs. For example, when an object of this type is constructed, but not when a pointer to this type is constructed.

On the other hand, for A<1> a;, A<1> is required to be a complete type (to construct a), then implicit instantiation happens, static_assert is fired.

EDIT

You can use sizeof (which requires the type to be complete) to cause the implicit instantiation and fire the static_assert. e.g.

template <class T>
class B{
static_assert(sizeof(T) > 0, "static_assert for implicit instantiation");
};

C++11 static_assert: Parameterized error messages

You cannot do this. static_assert wants a string literal. You have no way to assemble the semantic identity of T and Y into the string literal.

You can hope that the compiler gives an easy to read backtrace of the template instantiation stack and gives you the value of T and Y template parameters of the enclosing class template instantiation.

Other people thought about this too, see http://comments.gmane.org/gmane.comp.compilers.clang.devel/5073 for example.

Disallow a specific function template instantiation

I would use a static assert within your function call to create the proper failure during function instantiation:

template<typename T>
class is_double{ static const int value = false; }

template<>
class is_double<double>{ static const int value = true; }

template<typename T>
T convert( const char *argument ){
BOOST_STATIC_ASSERT( !is_double<T>::value );
//rest of code
}

And that should work within a function.



Related Topics



Leave a reply



Submit