Forwarding a non-type argument causes different behaviour on Variable Template

249 views Asked by At

This seems to be another "who's doing it well?" question since gcc 6.0.0 and clang 3.7.0 behaves different.

Let's suppose we have a variable template which takes a const char * as non template argument 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 have different types depending on the specialization. Ten we have two template functions, each of one takes a const char * as non template argument and forwards 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 this functions results in different behaviours:

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 was expecting) while gcc 6.0.0 fails to compile with the error below:

request for member 'function' in 'Value', which is of non-class type 'char [8]'

I'm almost sure that gcc failed to forward the template non-type argument NAME to the variable template Value in the function call_function and for this reason it selects the unspecialized variable template which is the one with 'char [8]' type...

It is acting like it is copying the template argument. This only happens when calling a member function of the object, if we comment the body of call_function, the output is FOO not UNKNOWN, so in the print function the forwarding is working even in gcc.

So

  • What's the correct behaviour? (mi bet is for clang)
  • How can I open a bug ticket for the compiler who's doing it wrong?
2

There are 2 answers

2
ecatmur On BEST ANSWER

There is a reasonable consensus that variable template specializations are permitted to alter the type of the variable template: C++1y/C++14: Variable Template Specialization?

The behavior of gcc is particularly interesting if the default type of Value is changed to a type with a 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();
     ^

The bug appears to be that where the non-specialized variable template has a type that is not dependent on the variable template template parameters, gcc assumes within template methods that use that variable template that the variable template always has that type.

The workaround, as usual, is to unconditionally forward the variable template to a class template with specialization(s) of the class template, and with the necessary fiddling for ODR compliance.

Another (possibly easier) workaround is to make the non-specialized variable template type somehow dependent on the variable template template parameters; in your case this would work:

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

I can't find a corresponding issue in gcc bugzilla so you might want to enter a new one. Here's 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>(); }
0
TartanLlama On

The interesting thing is that GCC is even self-contradictory in this example.

Lets declare an incomplete template class which should give is some nice compiler messages that we can abuse:

template <typename T>
struct type_check;

We'll also make another const char* that we can use for testing:

constexpr char NOT_FOO[]{"NOT_FOO"};

Now we'll see what the compiler chokes on:

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 which GCC 5.1.0 produces (edited a 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's take these one 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 can see that GCC correctly deduces that the type of Value<FOO> is 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 correctly does the forwarding and works out 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 fine, Value<FOO> is Foo, which we can call 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;

This is the odd one. Even though in error 2 we can see that GCC knows that the type of Value<NAME> is Foo, when it tries to do the lookup for the foo function, it gets it wrong and uses the primary template instead. This could be some bug in the function lookup which doesn't correctly resolve the values of non-type template arguments.

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 can see the compiler correctly choose the primary template when working out what Value<NOT_FOO> is. The thing that interests me is the (const char*)(& NOT_FOO)) which GCC deduces as the type of NOT_FOO. Maybe this is a pointer to the issue? I'm not sure.


I would suggest filing a bug and pointing out the discrepancy. Maybe this doesn't fully answer your question, but I hope it helps.