转发非类型参数会导致变量模板上的不同行为

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

这似乎是另一个 "who's doing it well?" 问题,因为 gcc 6.0.0 和 clang 3.7.0 的行为不同。

假设我们有一个变量模板,它接受一个 const char * 作为非模板参数并且专用于给定的指针:

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

请注意,模板变量根据专业化有不同的类型。 10 我们有两个模板函数,每个函数都接受一个 const char * 作为非模板参数,然后 转发 到变量模板:

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

template <const char *NAME> void call_function()
{
    Value<NAME>.function();
}

然后,调用此函数会导致不同的行为:

int main()
{
    print<INSTANCE_NAME>();
    call_function<INSTANCE_NAME>();

    return 0;
}

Code Here

clang 3.7.0 打印 FOOvoid Struct::function() const(如我所料),而 gcc 6.0.0 无法编译并出现以下错误:

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

我几乎可以肯定 gcc 未能 转发 模板非类型参数 NAME 到函数中的变量模板 Value call_function 并且出于这个原因,它选择了具有 'char [8]' 类型的非专用变量模板...

它的行为就像是在复制模板参数。这只发生在调用对象的成员函数时,如果我们注释 call_function 的主体,输出是 FOO 而不是 UNKNOWN,所以在 print 函数中 转发 即使在 gcc 中也能正常工作。

所以

允许变量模板特化改变变量模板的类型是一个合理的共识:C++1y/C++14: Variable Template Specialization?

如果将 Value 的默认类型更改为具有 function 方法的类型,gcc 的行为将特别有趣:

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();
     ^

该错误似乎是在非专用变量模板具有不依赖于变量模板模板参数的类型的情况下,gcc 假定在使用该变量模板的模板方法中该变量模板始终具有该类型.

像往常一样,解决方法是无条件地将变量模板转发到具有 class 模板特化的 class 模板,并为 ODR 合规性进行必要的调整。

另一种(可能更简单)解决方法是使非专用变量模板类型以某种方式依赖于变量模板模板参数;在你的情况下,这会起作用:

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

我在 gcc bugzilla 中找不到相应的问题,所以你可能想输入一个新问题。这是一个最小的例子:

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

有趣的是,GCC在这个例子中甚至是自相矛盾的。

让我们声明一个不完整的模板class,它应该提供一些我们可以滥用的不错的编译器消息:

template <typename T>
struct type_check;

我们还将制作另一个可用于测试的 const char*

constexpr char NOT_FOO[]{"NOT_FOO"};

现在我们来看看编译器阻塞了什么:

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

以下是 GCC 5.1.0 产生的错误(为清楚起见进行了一些编辑):

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;

让我们一次拿走这些。


错误 1:

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

在第一个错误中,我们可以看到 GCC 正确地推断出 Value<FOO> 的类型是 Foo。这就是我们所期望的。

错误2:

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

在这里,GCC 正确地进行了转发并计算出 Value<NAME>Foo.

类型

错误3:

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

很好,Value<NOT_FOO>"UNKNOWN",所以这是正确的。

错误 4:

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

这很好,Value<FOO>Foo,我们可以调用foo,返回void

错误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;

这很奇怪。尽管在错误 2 中我们可以看到 GCC 知道 Value<NAME> 的类型是 Foo,但当它尝试查找 foo 函数时,它弄错了并使用代替主模板。这可能是函数查找中的一些错误,它不能正确解析非类型模板参数的值。

错误 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;

在这里我们可以看到编译器在计算出 Value<NOT_FOO> 是什么时正确地选择了主模板。令我感兴趣的是 (const char*)(& NOT_FOO)),GCC 将其推断为 NOT_FOO 的类型。也许这是指向问题的指针?我不确定。


我建议提交错误并指出差异。也许这不能完全回答你的问题,但我希望它能有所帮助。