【问题标题】:Forwarding a non-type argument causes different behaviour on Variable Template转发非类型参数会导致变量模板的不同行为
【发布时间】:2015-06-18 07:22:48
【问题描述】:

这似乎是另一个“谁做得好?”问题因为 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 打印 FOOvoid Struct::function() const(如我所料)而 gcc 6.0.0 无法编译并出现以下错误:

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

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

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

所以

  • 正确的行为是什么? (mi 赌注是为了铿锵声)
  • 如何为做错的编译器开一个错误单?

【问题讨论】:

  • @BЈовић 可以,只要const char * 有外部链接(see this answer)。通过外部链接,它总是有相同的地址;把它想象成int
  • 请注意:具有完全不同的结构,称为FOOFoofoo,这使得您很难在心理上解析您的示例。 MyCharPMyStructmyFun 或类似的会更容易。
  • @TartanLlama 我已经改了名字,谢谢你的建议 :)
  • 即使没有在main 中调用call_function&lt;INSTANCE_NAME&gt;(),gcc 似乎也会尝试实例化call_function
  • 您是否需要将模板参数设置为const char* 才能使用? int 的专业化为 0 会起作用吗?

标签: c++ c++14 template-specialization variable-templates


【解决方案1】:

有趣的是,在这个例子中 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;

让我们一次拿这些。


错误 1:

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

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

错误 2:

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

在这里,GCC 正确地进行了转发,并确定 Value&lt;NAME&gt; 的类型为 Foo

错误 3:

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

很好,Value&lt;NOT_FOO&gt;"UNKNOWN",所以这是正确的。

错误 4:

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

这很好,Value&lt;FOO&gt;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&lt;NAME&gt; 的类型是 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&lt;NOT_FOO&gt; 是什么时正确地选择了主模板。我感兴趣的是(const char*)(&amp; NOT_FOO)),GCC 将其推断为NOT_FOO 的类型。也许这是一个指向问题的指针?我不确定。


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

【讨论】:

    【解决方案2】:

    有一个合理的共识是允许变量模板特化来改变变量模板的类型: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 在使用该变量模板的模板方法中假定变量模板始终具有该类型。

    与往常一样,解决方法是无条件地将变量模板转发到具有类模板特化的类模板,并为 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>(); }
    

    【讨论】:

    • 我已经添加了问题(希望我做得很好,毕竟这是我的第一个错误报告)我使用了你的例子和你的文字(因为英语不是我的第一语言,我我不确定我是否写了一些奇怪的东西),非常感谢。
    • @PaperBirdMaster 看起来不错;我添加了错误输出。 gcc.gnu.org/bugzilla/show_bug.cgi?id=66596
    猜你喜欢
    • 2016-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-07
    相关资源
    最近更新 更多