【问题标题】:Using std::enable_if with anonymous type parameters使用带有匿名类型参数的 std::enable_if
【发布时间】:2016-10-25 21:53:32
【问题描述】:

我尝试将std::enable_if 与未使用且未命名的类型参数一起使用,以免扭曲return 类型。但是,以下代码无法编译。

#include <iostream>

template <typename T, typename = std::enable_if_t<!std::is_integral<T>::value>>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
  foo<float>();
  foo<int>();
}

编译器说:

7:3: error: redefinition of 'template<class T, class> T foo()'
4:3: note: 'template<class T, class> T foo()' previously declared here
 In function 'int main()':
11:12: error: no matching function for call to 'foo()'
11:12: note: candidate is:
4:3: note: template<class T, class> T foo()
4:3: note: template argument deduction/substitution failed:

这里有什么问题?我如何更改代码才能编译? “发现现代 C++”教科书明确鼓励使用带有匿名类型参数的 std::enable_if

编辑:我知道如果我将std::enable_if 放入返回类型中它会起作用。但是,我的目的是获取更多详细信息,如果我将它与匿名类型参数一起使用,它为什么不起作用。正如我所说,我的教科书鼓励使用匿名类型参数的变体,所以我想知道为什么我的代码无法编译。

【问题讨论】:

标签: c++ c++11 templates sfinae enable-if


【解决方案1】:

但是,我的目的是了解更多详细信息,如果我将它与匿名类型参数一起使用,它为什么不起作用。

默认值不参与重载决议,因此您实际上是在重新定义相同的函数。

让我们简化您的示例:

template<typename = int>
void f() {}

template<typename = void>
void f() {}

int main() {
    f<>();
}

上面的代码无法编译,因为它不知道你想调用哪个版本的f

在您的情况下,如果我将 foo 调用为 foo&lt;void, void&gt;,我几乎会遇到同样的问题。
编译器猜不出我的意图,第二个参数有默认值并不意味着你不能传入不同的类型。

因此,代码格式错误,编译器会正确地给您一个错误。


附带说明,您仍然可以在不使用返回类型中的std::enable_if_t 的情况下使其正常工作。
举个例子:

#include <type_traits>
#include <iostream>

template <typename T, std::enable_if_t<!std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
    foo<float>();
    foo<int>();
}

当我试图弄清楚 OP 的(错误)假设是什么并解释为什么会出现这种情况时,@T.C.正确指出了 cmets 对此答案的实际原因。
值得引用他的评论来为答案添加更多细节:

这不是重载决议;它的声明匹配。首先没有两个重载会出现任何歧义。这是两个重定义错误:函数模板和默认模板参数。

【讨论】:

  • 忘记调用;问题是您正在重新定义相同的功能模板。
  • @T.C.我在第一行说过。该调用是另一个示例,说明如果允许它会如何产生问题。我应该从答案中删除它吗?
  • 不是重载解析;它的声明匹配。首先没有两个重载会出现任何歧义。这是两个重定义错误:函数模板和默认模板参数。
  • @T.C.我明白你的意思,我并不是说你错了(好吧,我想我永远不会说你实际上是错的 - 技能差距很明显),但我试图找出 OP 的错误推理(在我的拙见)模板参数的默认值参与重载决议。就这样。如果您同意,我会将答案与您的评论结合起来以提供更多详细信息。
【解决方案2】:

您可以将enable_if 放入返回类型中:

template <typename T>
std::enable_if_t<!std::is_integral<T>::value,T>
foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
std::enable_if_t<std::is_integral<T>::value, T>
foo() { std::cout << "integral" << std::endl; return T(); }

顺便说一句,enable_if_t 可以从 C++14 获得,所以你可能想说typename std::enable_if&lt;std::is_integral&lt;T&gt;::value, T&gt;::type 来代替。好拗口。

但更惯用(和可读)的是根据类型进行调度:

template <typename T>
T foo_impl(std::false_type) { std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
T foo_impl(std::true_type) { std::cout << "integral" << std::endl; return T(); }

template <typename T>
T foo(){
    return foo_impl<T>(typename std::is_integral<T>::type{});
}

【讨论】:

    【解决方案3】:

    您可以通过多种方式使用 SFINAE away 函数。您通常应该避免添加额外的函数/模板参数,而只是与返回类型混合。

    template <typename T>
    auto foo() -> std::enable_if_t<!std::is_integral<T>::value, T>
    { std::cout << "non-integral" << std::endl; return T(); }
    
    template <typename T>
    auto foo() -> std::enable_if_t<std::is_integral<T>::value, T>
    { std::cout << "integral" << std::endl; return T(); }
    

    【讨论】:

    • 我知道如果我将std::enable_if 放入返回类型中它会起作用。但是,我的目的是获取更多详细信息,如果我将它与匿名类型参数一起使用,它为什么不起作用。正如我所说,我的教科书鼓励使用匿名类型参数的变体,所以我想知道为什么我的代码无法编译。看来你鼓励相反。原因是什么?
    • @user1494080 我鼓励相反,因为在上面的答案中添加额外的模板参数或额外的函数参数可以让用户这样做:foo&lt;float, int&gt;();foo&lt;float&gt;(nullptr); 我们不希望那样被允许。它可以破坏东西。另外,返回类型 SFINAE 很少会出错。
    【解决方案4】:

    您的错误是您在等号右侧使用enable_if_t

    你必须在左边使用它

    #include <iostream>
    #include <type_traits>
    
    template <typename T, std::enable_if_t<!std::is_integral<T>::value, int> = 0>
    T foo() { std::cout << "non-integral" << std::endl; return T(); }
    
    template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
    T foo() { std::cout << "integral" << std::endl; return T(); }
    
    int main() {
      foo<float>();
      foo<int>();
    }
    

    但这适用于 C++14。

    在 C++11 中(你的问题被标记为 C++11)你没有enable_if_t

    代码变成

    #include <iostream>
    #include <type_traits>
    
    template <typename T,
              typename std::enable_if<!std::is_integral<T>::value, int>::type = 0>
    T foo() { std::cout << "non-integral" << std::endl; return T(); }
    
    template <typename T,
              typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
    T foo() { std::cout << "integral" << std::endl; return T(); }
    
    int main() {
      foo<float>();
      foo<int>();
    }
    

    【讨论】:

    • “你必须在左边使用它”
    • @Barry - 抱歉:不确定是否理解:“不正确”是否意味着他可以以其他方式使用std::enable_if_t(例如:启用(或不启用)返回值)?
    • 不,我是说事实上你可以使用enable_if_t 作为默认类型(就像你可以使用任何其他类型一样)。这里的错误是 OP 试图为 same 函数模板提供两种不同的默认类型。
    • @Barry - 我试着用我的话来表达(如果我错了,请纠正我)。 OP 定义了两个版本的f() 具有相同的模板签名(typenametypename);这会产生错误,因为 sfinae 可以为第二个 typename 参数提供不同的默认值,但不能更改签名;这会产生冲突,编译器无法选择并给出错误。在我的示例中,sfinae 启用或不启用第二个 typename 参数,因此更改(或不更改)函数模板签名;所以编译器会看到一个f()(对于每次调用)并且编译没有错误。
    猜你喜欢
    • 1970-01-01
    • 2021-03-25
    • 1970-01-01
    • 2021-06-02
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多