当前位置: 首页 > 知识库问答 >
问题:

具有依赖类型的 c 11 可变参数函数模板重载是否模棱两可?

陈君之
2023-03-14

以下代码是递归变量函数重载的教科书示例。在clang和GCC中,它编译干净,main返回36(如预期的那样):

template <typename T>
int add(T val)
{
    return val;
}

template <typename FirstTypeT, typename... RestT>
int add(FirstTypeT first_value, RestT... rest)
{
    return first_value + add<RestT...>(rest...);
}

int main(void)
{
    return add(12, 12, 12);
}

然而,这里有一个小小的修改。它在模板定义中使用依赖类型,而不是直接使用模板参数:

struct Foo
{
    using SomeType = int;
};

template <typename T>
int add(typename T::SomeType val)
{
    return val;
}

template <typename FirstT, typename... RestT>
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest)
{
    return first_value + add<RestT...>(rest...);
}

int main(void)
{
    return add<Foo, Foo, Foo>(12, 12, 12);
}

它使用GCC 5.2编译并运行,但使用clang 3.8时失败:

clang++ variadic.cpp -o var -std=c++11 -Wall
variadic.cpp:15:26: error: call to 'add' is ambiguous
    return first_value + add<RestT...>(rest...);
                         ^~~~~~~~~~~~~
variadic.cpp:15:26: note: in instantiation of function template specialization 'add<Foo, Foo>' requested here
    return first_value + add<RestT...>(rest...);
                         ^
variadic.cpp:20:12: note: in instantiation of function template specialization 'add<Foo, Foo, Foo>' requested here
    return add<Foo, Foo, Foo>(12, 12, 12);
           ^
variadic.cpp:7:5: note: candidate function [with T = Foo]
int add(typename T::SomeType val)
    ^
variadic.cpp:13:5: note: candidate function [with FirstT = Foo, RestT = <>]
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest)
    ^
1 error generated.

我的问题是双重的。

  1. 是否真的有效使用参数包 typename 模式将范围解析运算符应用于包的每个成员,如 typename RestT::SomeType...
  2. clang 相对于标准是否正确,或者这是一个错误?第二个例子真的比第一个例子更模糊吗?(对于第一个示例,似乎你可以说单个参数重载是模棱两可的,第二个参数用 RestT = 实例化

共有2个答案

卢文博
2023-03-14

错误消息已经显示了原因。

当生成add(12)时,有两个可用的模板函数。那是

template <typename T>
int add(typename T::SomeType val);

template <typename FirstT, typename... RestT>
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest);
// and RestT is empty here(RestT = <>)

这不是标准用法,叮当是正确的。

请考虑此代码。

#include <tuple>
#include <type_traits>

struct Foo
{
    using SomeType = int;
};

// helper function to sum a tuple of any size
template<typename Tuple, std::size_t N>
struct TupleSum {
    typedef typename std::tuple_element<N - 1, Tuple>::type ref_t;
    typedef typename std::remove_reference<ref_t>::type noref_t;

    static noref_t sum(const Tuple& t) 
    {
        return std::get<N - 1>(t) + TupleSum<Tuple, N - 1>::sum(t);
    }
};

template<typename Tuple>
struct TupleSum<Tuple, 1> {
    typedef typename std::tuple_element<0, Tuple>::type ref_t;
    typedef typename std::remove_reference<ref_t>::type noref_t;

    static noref_t sum(const Tuple& t) 
    {
        return std::get<0>(t);
    }
};

template <typename... RestT>
int add(typename RestT::SomeType... rest) {
    typedef decltype(std::forward_as_tuple(rest...)) tuple_t;
    return TupleSum<tuple_t, sizeof...(RestT) >::sum(std::forward_as_tuple(rest...));
}

int main(void)
{
    return add<Foo, Foo, Foo>(12, 12, 12);
}
公沈浪
2023-03-14

> < li >是的,很好。 < li>

当前的措辞在这一点上非常明确:在部分订购期间,参数包被完全忽略,因为它没有任何参数([temp.deduct.partial]/(3.1))。[temp.func.order]/5也给出了一个非常中肯的例子,即使有可推导的模板参数——表明你的第一个例子也是模棱两可的:

[注意:由于调用上下文中的部分排序只考虑有显式调用参数的参数,因此某些参数会被忽略(即函数参数包、带默认参数的参数和省略号参数)。[...] [例如:

template<class T, class... U> void f(T, U ...);  // #1
template<class T            > void f(T       );  // #2

void h(int i) {
  f(&i); // error: ambiguous
  // [...]
}

然而,这不是最佳的。关于变量模板偏序有一个核心问题1395:

CWG同意这个例子应该被接受,把这个例子作为最后的决胜局来处理,比起一个参数包,他更喜欢一个省略的参数。

(第1825期给出了一个更精细的策略。两个编译器都为第一种情况实现此规则;只有海湾合作委员会对第二个(即可以被认为是领先半步)。

 类似资料:
  • 在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。 可变参数模板 可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号...: template<ty

  • 我希望维护语法,但似乎只能将typenames放在括号中,而不能将类放在括号中。以下是我当前的代码: 问题1:我找不到一种方法来使“default”参数对于特殊化不是必需的。我尝试了使用默认构造函数的代理类,将第三个参数更改为指针,并指定nullptr(这在语法上并不理想)和对类型的常量引用(这仍然需要用户端的三个参数),但似乎没有任何东西允许在中接受两个参数。 问题2:我找不到正确的语法来获得混

  • 请考虑以下定义和演绎指南: 如果我尝试使用显式模板参数实例化 ,代码将正确编译: 如果我试图通过演绎指南实例化< code>foo... > G7产生编译器错误: clang 5 爆炸: wandbox上的实例 虽然clang肯定被窃听了(报告为问题#32673),但g拒绝我的代码是正确的吗?我的代码格式错误吗?

  • 通常我知道如何修复不明确的类型变量问题,但这次我不知道了。长话短说,我使用protobuf Haskell库来处理协议缓冲区。该库使您忘记了单独维护。proto文件,如果数据类型分别是Encode和Decode类型类的实例,则派生序列化和反序列化数据类型的方法。 我正在protobuf之上设计一个简单的协议。有一个主消息数据类型,包含消息id、消息类型和一些取决于类型的可选数据。我想有一个函数来获

  • OpenGL定义了C函数来管理资源。我编写了一个简单的包装器来以RAII的方式处理它们。函数对类似于和。但是,也有一些函数对适用于资源数组,例如和。对于前者,我编写了一个简单的类来完成这项工作,对于后者,我编写了另一个处理数组的类。然而,我注意到有时我只使用一个缓冲区或纹理,在那里我不必承担向量的费用,我想如果发布函数在开始时采用大小参数,我会专门化类析构函数,但是... 对于上述SSCCE g树

  • 我对编程和C++相当陌生。我有一个函数,我想接受以模板化值作为参数的函数指针。我的意思是... 我有这个功能: 我试过的: 结果: .h:165:22:注意:已忽略候选模板:无法将“function ,allocator >&)”与“void(*)(bool,std::vectorstd::vectorstd::allocatorstd::tuple &)”void postgrescaller: