6

这似乎是另一个“谁做得好?” 问题因为 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>{};

请注意,模板变量具有不同的类型,具体取决于专业化。十我们有两个模板函数,每个都接受一个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 打印FOO并且void Struct::function() const(正如我所料)而 gcc 6.0.0 无法编译并出现以下错误:

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

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

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

所以

  • 什么是正确的行为?(mi 赌注是为了铿锵声)
  • 我怎样才能为做错的编译器打开错误票?
4

2 回答 2

5

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

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

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;

让我们一次拿这些。


错误一:

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. 也许这是一个指向问题的指针?我不知道。


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

于 2015-06-18T08:49:41.700 回答
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();
     ^

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

与往常一样,解决方法是无条件地将变量模板转发到具有类模板专业化的类模板,并为 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>(); }
于 2015-06-18T08:44:02.660 回答