我不擅长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);
}

但是,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>&’

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.

似乎很奇怪,因为它在显然应该是Rest的情况下将<>推导为<int>,并且它说上面一行中的<int>no known conversion from 'hnode<int>' to 'std::nullptr_t'。我犯了什么错误?

最佳答案

尽管galop1n的答案是有效的解决方法,但它不能解决代码中的实际问题。

尽管我不是C++语言律师或其他任何人,但是我在SO的某处读到模板匹配是一个相当严格的过程。反射(reflect)原始代码:在hcons函数中,您要求编译器将next(应该为hnode<typename...>类型)与hnode<typename, typename...>匹配。可以想象,这并不是完全匹配。它也与您的1个参数特化(即hnode<typename>)不匹配。

要使代码编译,您只需更改1件事,即提供一个空的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);
}

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

众所周知,Visual Studio的编译器在与模板相关的内容上有些松懈,所以这可能就是为什么它接受原始代码的原因。

正如可以在this相关问题中发现的那样,事实证明这是GCC和Clang中的错误,因此VS是可以正确接受您的代码的错误。

另外请注意:通过删除std::nullptr_t的1个参数专用化中的hnode成员,可以节省一点内存。

07-27 13:38