Clang和GCC错误推断模板参数

5

我不擅长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 functionint main()’:
test.cc:28:29: error: no matching function for call tohcons(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>,但是出现了从'hnode<int>'到'std::nullptr_t'的未知转换错误。我犯了什么错误?


hcons 的第一个重载不需要参数包。 - David G
@0x499602D2 哦,非常正确。我已经将其删除,但是仍然出现相同的错误。显然,具有不可推导的可变模板参数并不是问题,因此实际上并没有错误。 - user3175411
我不会从hcons的第一个重载中删除无用的typename... Rest,因为这样会破坏错误消息。尽管如此,它是无害的,也不是错误的原因。 - user3175411
当我在第二个重载非可变参数上进行“Rest…”操作时,它对我有效。虽然我无法解释为什么。 - David G
问题的最小化示例:http://coliru.stacked-crooked.com/a/c1ba60f915f12735 - dyp
可能是 https://dev59.com/a2Qm5IYBdhLWcg3w4CEN 的重复问题。 - Constructor
2个回答

4
虽然galop1n的答案是一个有效的解决方法,但它并没有解决你代码中的真正问题。
虽然我不是C++语言专家,但我在Stack Overflow上读到过模板匹配是一个相当严格的过程。回到你的原始代码:在你的hcons函数中,你要求编译器将next(应该是类型为hnode 的)与hnode 匹配。正如你所想象的那样,这并不是一个精确的匹配。它也不匹配你的一个参数特化,即hnode 。
要使你的代码编译,你只需要改变一件事情,那就是提供一个接受可变数量模板参数的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...>,并实例化相应的特化版本。在ideone上的POC中可以找到。

众所周知,Visual Studio的编译器在涉及模板相关内容时有些宽松,这可能是它接受你原始代码的原因。

正如这个相关问题中所发现的那样,这实际上是GCC和Clang中的一个bug,所以VS是正确地接受了你的代码。

另外需要注意的是:通过删除你的hnode的1个参数特化中的std::nullptr_t成员,你可以节省一点内存。


我在某个Stack Overflow(SO)的帖子上读到,模板匹配是一个相对严格的过程。如果你能提供一个链接,我会很高兴的。我了解有很多相关的问题,但在标准中从未找到一个明确的答案([temp.deduct.type]/9提到了一些相关内容)。 - dyp
1
@dyp 我找不到任何直接的结果,最接近的是这个问题,基本上归结于同样的问题,答案似乎表明这是Clang和GCC中的一个错误。 - Tom Knapen
谢谢。同时,相同的问题已经在原帖中作为可能的重复链接了 ;) - dyp
谢谢。我不想删除 nullptr_t 成员,因为我不想要求迭代列表的所有函数都专门针对单参数版本进行特化。 - user3175411

1

不确定原因,但由于hnode至少有一个模板参数,所以这个工作:

#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>
hnode<T> hcons(T&& val, std::nullptr_t) {
    return { std::forward<T>(val), nullptr };
}

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

int main() {
    hnode<int> three = hcons(1, nullptr);
    auto two = hcons("hi", three);
    auto one = hcons(5.14f, two);
}

在您的版本中,一旦无法找到最佳匹配,编译器列出所有候选项是正常的。真正的问题仍然存在,为什么clang在模板参数包推断方面是错误的...
Visual Studio 2013在两个版本(带和不带S)上都成功了,但在将const char (*) [3]向前替换为int进行测试时仍然失败...
Clang或Visual,谁是对的,我不知道,但在这里填写错误报告可能是一个好主意。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接