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

Pap*_*ter 6 c++ template-specialization variable-templates c++14

这似乎是另一个"谁做得好"?问题,因为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>{};
Run Code Online (Sandbox Code Playgroud)

请注意,模板变量具有不同的类型,具体取决于特化.十,我们有两个模板函数,每个函数都有一个const char *非模板参数,并将转发给变量模板:

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

template <const char *NAME> void call_function()
{
    Value<NAME>.function();
}
Run Code Online (Sandbox Code Playgroud)

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

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

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Code Here

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

请求'Value'中的成员'function',它是非类型'char [8]'

我几乎可以肯定gcc未能将模板非类型参数转发给函数NAME中的变量模板Value,call_function因此它选择了非特殊化的变量模板,它是具有'char [8]'类型的变量模板...

它就像复制模板参数一样.这只发生在调用对象的成员函数时,如果我们对主体进行注释call_function,则输出FOO不是UNKNOWN,所以在print函数中转发工作甚至在gcc中工作.

所以

  • 什么是正确的行为?(mi bet for clang)
  • 如何为编写错误的编译器打开错误票?

Tar*_*ama 5

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

让我们声明一个不完整的模板类,它应该给出一些我们可以滥用的好的编译器消息:

template <typename T>
struct type_check;
Run Code Online (Sandbox Code Playgroud)

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

constexpr char NOT_FOO[]{"NOT_FOO"};
Run Code Online (Sandbox Code Playgroud)

现在我们将看到编译器扼杀的内容:

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;
}
Run Code Online (Sandbox Code Playgroud)

以下是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;
Run Code Online (Sandbox Code Playgroud)

我们一次拿这些.


错误1:

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

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

错误2:

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

在这里,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;
Run Code Online (Sandbox Code Playgroud)

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

错误4:

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

这是好的,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;
Run Code Online (Sandbox Code Playgroud)

这是奇怪的.即使在错误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;
Run Code Online (Sandbox Code Playgroud)

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


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


eca*_*mur 3

有一个合理的共识,即变量模板特化允许改变变量模板的类型:C++1y/C++14:变量模板特化?

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

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();
     ^
Run Code Online (Sandbox Code Playgroud)

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

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

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

template <const char *P> decltype(*P)   Value[]{"UNKNOWN"};
Run Code Online (Sandbox Code Playgroud)

我在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>(); }
Run Code Online (Sandbox Code Playgroud)