Clang和GCC错误地模板参数

use*_*411 5 c++ templates variadic-templates c++11

我不擅长C++,所以这可能是一个新手错误.我正在尝试创建一个异构链表类型,其中每个节点的类型和列表其余部分的类型在每个节点中都是已知的.

这是一个SSSCE:

#include <utility>

template<typename T, typename... Rest>
struct hnode {
    T data;
    hnode<Rest...>* next;
};

template<typename T>
struct hnode<T> {
    T data;
    std::nullptr_t next;
};

template<typename T, typename... Rest>
hnode<T> hcons(T&& val, std::nullptr_t) {
    return { std::forward<T>(val), nullptr };
}

template<typename T, typename... Rest>
hnode<T, Rest...> hcons(T&& val, hnode<Rest...>& next) {
    return { std::forward<T>(val), &next };
}

int main() {
    hnode<int> three = hcons(1, nullptr);
    auto two = hcons("hi", three);
}
Run Code Online (Sandbox Code Playgroud)

但是,GCC给了我这个代码的错误:

test.cc: In function ‘int main()’:
test.cc:28:29: error: no matching function for call to ‘hcons(const char [3], hnode<int>&)’
 auto two = hcons("hi", three);
                             ^
test.cc:28:29: note: candidates are:
test.cc:17:10: note: template<class T, class ... Rest> hnode<T> hcons(T&&, std::nullptr_t)
 hnode<T> hcons(T&& val, std::nullptr_t) {
          ^
test.cc:17:10: note:   template argument deduction/substitution failed:
test.cc:28:29: note:   cannot convert ‘three’ (type ‘hnode<int>’) to type ‘std::nullptr_t’
 auto two = hcons("hi", three);
                             ^
test.cc:22:19: note: hnode<T, Rest ...> hcons(T&&, hnode<Rest ...>&) [with T = const char (&)[3]; Rest = {int, Rest}]
 hnode<T, Rest...> hcons(T&& val, hnode<Rest...>& next) {
                   ^
test.cc:22:19: note:   no known conversion for argument 2 from ‘hnode<int>’ to ‘hnode<int, Rest>&’
Run Code Online (Sandbox Code Playgroud)

Clang稍微简洁一些但仍然没有帮助我解决它:

test.cc:28:12: error: no matching function for call to 'hcons'
auto two = hcons("hi", three);
           ^~~~~
test.cc:17:10: note: candidate function [with T = char const (&)[3], Rest = <>] not viable: no known conversion from 'hnode<int>' to 'std::nullptr_t' (aka 'nullptr_t') for 2nd argument
hnode<T> hcons(T&& val, std::nullptr_t) {
         ^
test.cc:22:19: note: candidate template ignored: substitution failure [with T = char const (&)[3], Rest = <>]: too few template arguments for class template 'hnode'
hnode<T, Rest...> hcons(T&& val, hnode<Rest...>& next) {
                  ^              ~~~~~
1 error generated.
Run Code Online (Sandbox Code Playgroud)

这看起来很奇怪,因为它的推断Rest应该是<>它应该清楚的时候<int>,而且它表明它已经<int>在上面了no known conversion from 'hnode<int>' to 'std::nullptr_t'.我犯了什么错误?

Tom*_*pen 4

虽然 galop1n 的答案是一个有效的解决方法,但它并没有解决代码中的真正问题。

虽然我不是 C++ 语言律师或任何其他人,但我在 SO 上读到过模板匹配是一个相当严格的过程。反思您的原始代码:在您的hcons函数中,您要求编译器匹配next(应该是 类型hnode<typename...>)到hnode<typename, typename...>。正如您可以想象的那样,这并不是真正的完全匹配。它也不符合您的 1 个参数专业化,即hnode<typename>.

要使代码编译,您只需要更改一件事,那就是提供一个空声明,hnode接受可变数量的模板参数,并将其专门用于 1 参数版本和多参数版本:

#include <utility>

// empty declaration with variable number of arguments
template<typename...>
struct hnode;

// specialization for 1 template argument
template<typename T>
struct hnode<T> {
    T data;
    std::nullptr_t next;
};

// specialization for multiple template arguments
template<typename T, typename... Rest>
struct hnode<T, Rest...> {
    T data;
    hnode<Rest...>* next;
};

template<typename T>
hnode<T> hcons(T&& val, std::nullptr_t) {
    return { std::forward<T>(val), nullptr };
}

template<typename T, typename... Rest>
hnode<T, Rest...> hcons(T&& val, hnode<Rest...>& next) {
    return { std::forward<T>(val), &next };
}

int main() {
    hnode<int> three = hcons(1, nullptr);
    auto two = hcons("hi", three);
}
Run Code Online (Sandbox Code Playgroud)

现在您的hcons函数可以hnode<typename...>完全匹配,并将实例化相应的专业化。ideone 上的 POC

众所周知,Visual Studio 的编译器在处理与模板相关的内容时有点宽松,因此这可能是它接受原始代码的原因。

正如可以在这个相关问题中找到的,事实证明这是 GCC 和 Clang 中的一个错误,因此 VS 是正确接受您的代码的。

另请注意:通过删除std::nullptr_t1 个参数特化中的成员hnode,您可以节省一点内存。