Forwarding a non-type argument causes different behavior in the variable template

This seems to be another โ€œwho does it well?โ€ question, since gcc 6.0.0 and clang 3.7.0 behave differently.

Suppose we have a variable template that takes const char * as an argument without a template and is specialized for a given pointer:

 constexpr char INSTANCE_NAME[]{"FOO"}; struct Struct{ void function() const { std::cout << __PRETTY_FUNCTION__; } }; std::ostream &operator <<(std::ostream &o, const Struct &) { return o << INSTANCE_NAME; } template <const char *> char Value[]{"UNKNOWN"}; // spezialization when the pointer is INSTANCE_NAME template < > Struct Value<INSTANCE_NAME>{}; 

Note that the template variable has different types depending on the specialization. Ten, we have two template functions, each of which takes const char * as an argument without a template and redirects it to the variable template:

 template <const char *NAME> void print() { std::cout << Value<NAME> << '\n'; } template <const char *NAME> void call_function() { Value<NAME>.function(); } 

Then calling these functions leads to various types of behavior:

 int main() { print<INSTANCE_NAME>(); call_function<INSTANCE_NAME>(); return 0; } 

Code here

clang 3.7.0 prints FOO and void Struct::function() const (as I expected), while gcc 6.0.0 fails to compile with the error below:

query for the member function 'in' Value ', which belongs to the nonclass class' char [8]'

I am pretty sure that gcc was unable to send the non-type argument of the NAME template to the variable template Value in the call_function function, and for this reason it selects a template for a non-specialized variable that is one with the 'char [8]' type ...

It acts as if it is copying a template argument. This only happens when the member function of the object is called, if we comment on the call_function body, the output is FOO not UNKNOWN , so forwarding in the print function works even in gcc.

So,

  • What is the correct behavior? (mi bet for clang)
  • How can I open big code for a compiler that does it wrong?
+6
source share
2 answers

There is a reasonable consensus that specializations with variable templates are allowed to change the type of variable template: C ++ 1y / C ++ 14: Specialization of variable templates?

The behavior of gcc is particularly interesting if the default value type is changed to a type using the function method:

 struct Unknown{ void function() const { std::cout << __PRETTY_FUNCTION__; } }; template <const char *> Unknown Value; prog.cc: In instantiation of 'void call_function() [with const char* NAME = ((const char*)(& INSTANCE_NAME))]': prog.cc:26:18: required from here prog.cc:20:5: error: 'Unknown::function() const' is not a member of 'Struct' Value<NAME>.function(); ^ 

It seems that the error is that in the case where the template of a non-specialized variable has a type that does not depend on the parameters of the template of the variable template, gcc accepts in the template methods that use this variable template that the variable template always has this type.

The workaround, as usual, is to unconditionally redirect the variable template to the class template with the specialization (s) of the class template and with the necessary function to meet ODR requirements.

Another (possibly simpler) solution is to create a non-specialized type of variable template, which somehow depends on the template template template parameters; in your case, this will work:

 template <const char *P> decltype(*P) Value[]{"UNKNOWN"}; 

I cannot find the corresponding problem in gcc bugzilla so you might want to introduce a new one. Here is a minimal example:

 struct U { void f() {} }; struct V { void f() {} }; template<class T> U t; template<> V t<int>; template<class T> void g() { t<T>.f(); } int main() { g<int>(); } 
+3
source

Interestingly, the GCC in this example is even contradictory.

Lets you declare an incomplete template class that you must provide, these are some good compiler messages we can abuse:

 template <typename T> struct type_check; 

We will also create another const char* that we can use for testing:

 constexpr char NOT_FOO[]{"NOT_FOO"}; 

Now we will see that the compiler is suffocating:

 template <const char *NAME> void foo() { type_check<decltype(Value<FOO>)> a; type_check<decltype(Value<NAME>)> b; type_check<decltype(Value<NOT_FOO>)> c; type_check<decltype(Value<FOO>.foo())> d; type_check<decltype(Value<NAME>.foo())> e; type_check<decltype(Value<NOT_FOO>.foo())> f; } 

Here are the errors that GCC 5.1.0 produces (edited bit for clarity):

 test.cpp:21:38: error: 'type_check<Foo> a' has incomplete type type_check<decltype(Value<FOO>)> a; ^ test.cpp:22:39: error: 'type_check<Foo> b' has incomplete type type_check<decltype(Value<NAME>)> b; test.cpp:25:42: error: 'type_check<char [8]> c' has incomplete type type_check<decltype(Value<NOT_FOO>)> c; ^ test.cpp:23:44: error: 'type_check<void> c' has incomplete type type_check<decltype(Value<FOO>.foo())> c; test.cpp:24:37: error: request for member 'foo' in 'Value<NAME>', which is of non-class type 'char [8]' type_check<decltype(Value<NAME>.foo())> d; test.cpp:28:40: error: request for member 'foo' in 'Value<((const char*)(& NOT_FOO))>', which is of non-class type 'char [8]' type_check<decltype(Value<NOT_FOO>.foo())> f; 

Let them take this data at a time.


Error 1:

 test.cpp:21:38: error: 'type_check<Foo> a' has incomplete type type_check<decltype(Value<FOO>)> a; 

In the first error, we see that GCC correctly infers that the type Value<FOO> is equal to Foo . This is what we expect.

Error 2:

 test.cpp:22:39: error: 'type_check<Foo> b' has incomplete type type_check<decltype(Value<NAME>)> b; 

Here GCC does the forwarding correctly and it works that Value<NAME> is of type Foo .

Error 3:

 test.cpp:25:42: error: 'type_check<char [8]> c' has incomplete type type_check<decltype(Value<NOT_FOO>)> c; 

Great, Value<NOT_FOO> is "UNKNOWN" , so this is correct.

Error 4:

 test.cpp:23:44: error: 'type_check<void> c' has incomplete type type_check<decltype(Value<FOO>.foo())> c; 

This is normal, Value<FOO> is Foo , which can be called Foo on, returning void .

Error 5:

 test.cpp:24:37: error: request for member 'foo' in 'Value<NAME>', which is of non-class type 'char [8]' type_check<decltype(Value<NAME>.foo())> d; 

It is odd. Although with error 2, we see that GCC knows that the type Value<NAME> is Foo , when it tries to search for the function Foo , it is mistaken and uses the primary template instead. This may be some mistake in finding a function that incorrectly solves the arguments of a non-piggy type template.

Error 6:

 test.cpp:28:40: error: request for member 'foo' in 'Value<((const char*)(& NOT_FOO))>', which is of non-class type 'char [8]' type_check<decltype(Value<NOT_FOO>.foo())> f; 

Here we see that the compiler correctly selects the main template when developing Value<NOT_FOO> . I'm interested in (const char*)(& NOT_FOO)) , which GCC outputs as type NOT_FOO . Maybe this is a pointer to this problem? I'm not sure.


I would suggest making a mistake and pointing out a discrepancy. This may not fully answer your question, but I hope this helps.

+5
source

Source: https://habr.com/ru/post/989326/


All Articles