【问题标题】:Execute function inside function template only for those types that have the function defined仅对定义了函数的类型在函数模板中执行函数
【发布时间】:2020-05-23 12:35:47
【问题描述】:

我有一个函数模板,它接受许多不同类型的输入。在这些类型中,只有一种具有getInt() 函数。因此,我希望代码仅针对该类型运行该函数。请提出解决方案。谢谢

#include <type_traits>
#include <typeinfo>

class X {
    public:
    int getInt(){
        return 9;
    }
};

class Y{

};

template<typename T>
void f(T& v){
    // error: 'class Y' has no member named 'getInt'
    // also tried std::is_same<T, X>::value 
    if(typeid(T).name() == typeid(X).name()){
        int i = v.getInt();// I want this to be called for X only
    }
}

int main(){
    Y y;
    f(y);
}

【问题讨论】:

  • 与您的问题无关,但type_info 结构具有equality comparison operator,因此typeid(T) == typeid(X) 应该也可以工作。
  • 使用:if constexpr,条件为is_same_v&lt;T,X&gt;
  • 今年晚些时候,Concepts 的解决方案将正式变得更加优雅。我知道,现在不是很有帮助。
  • 有很多方法可以解决您的问题。上面提到的一对。您还可以使用不同变体的 traits 来查看类型是否具有可调用的 getInt 成员。仅在 stackoverflow.com 上,如果您稍微搜索一下,如何查看结构或类是否具有特定的成员函数,这里肯定有很多问题。

标签: c++ c++11 templates c++14 metaprogramming


【解决方案1】:

如果您希望能够为所有具有函数成员 getInt 而不仅仅是 X 的类型调用函数 f,您可以为函数 f 声明 2 个重载:

  1. 对于具有getInt 成员函数的类型,包括类X

  2. 适用于所有其他类型,包括 Y 类。

C++11/C++17解决方案

考虑到这一点,您可以这样做:

#include <iostream>
#include <type_traits>

template <typename, typename = void>
struct has_getInt : std::false_type {};

template <typename T>
struct has_getInt<T, std::void_t<decltype(((T*)nullptr)->getInt())>> : std::is_convertible<decltype(((T*)nullptr)->getInt()), int>
{};

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T,
          typename std::enable_if<!has_getInt<T>::value, T>::type* = nullptr>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <typename T,
          typename std::enable_if<has_getInt<T>::value, T>::type* = nullptr>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

查看live

请注意std::void_t是在C++17中引入的,但如果你仅限于C++11,那么自己实现void_t真的很容易:

template <typename...>
using void_t = void;

这里是 C++11 版本live

我们在 C++20 中有什么?

C++20 带来了很多好东西,其中之一就是concepts。以上对 C++11/C++14/C++17 有效的东西在 C++20 中可以显着减少:

#include <iostream>
#include <concepts>

template<typename T>
concept HasGetInt = requires (T& v) { { v.getInt() } -> std::convertible_to<int>; };

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <HasGetInt T>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

查看live

【讨论】:

  • 在 C++17 之前,void_t 的实现会导致一些旧编译器出现问题(如链接所指)。
  • 不一定要写两个重载(恕我直言,用“can”代替“need”会更好)
  • 我想问点不一样的,第二个函数模板是如何为 f(y) 选择的。不是两个模板函数都适合 f(y) 吗?
  • 我认为 Concepts 是单数的(如“其中一个是 Concepts”或“Concepts 是一项新功能”)
  • 概念定义不准确。您将结果分配给 int 所以概念应该是 template&lt;typename T&gt; concept HasGetInt = requires (T&amp; v) { {v.getInt()} -&gt; std::convertible_to&lt;int&gt;; };
【解决方案2】:

你可以使用 C++17 中的if constexpr

template<typename T>
void f(T& v){
    if constexpr(std::is_same_v<T, X>) { // Or better create trait has_getInt
        int i = v.getInt();// I want this to be called for X only
    }
    // ...
}

在此之前,您将不得不使用重载和 SFINAE 或标签调度。

【讨论】:

  • if constexpr 是 C++17 功能。
  • @NutCracker:更新标签/问题并因此使现有答案无效......(即使警告很好)。
  • 我刚刚更新了标签...问题标题已由 OP 更新
【解决方案3】:

保持简单和超载。至少从 C++98 开始工作......

template<typename T>
void f(T& v)
{
    // do whatever
}

void f(X& v)
{
    int result = v.getInt();
}

如果只有一种类型具有getInt 函数,这就足够了。如果还有更多,那就不再那么简单了。有几种方法可以做到,这里是一种:

struct PriorityA { };
struct PriorityB : PriorityA { };

template<typename T>
void f_impl(T& t, PriorityA)
{
    // generic version
}

// use expression SFINAE (-> decltype part)
// to enable/disable this overload
template<typename T>
auto f_impl(T& t, PriorityB) -> decltype(t.getInt(), void())
{
    t.getInt();
}

template<typename T>
void f(T& t)
{
    f_impl(t, PriorityB{ } ); // this will select PriorityB overload if it exists in overload set
                              // otherwise PriorityB gets sliced to PriorityA and calls generic version
}

Live example with diagnostic output.

【讨论】:

  • 在这种情况下,这会起作用,因为只有一个重载(对于 X),但是,如果将来有更多类似的类型与成员 getInt,这不是这样的好的做法。您可能需要注意
  • @NutCracker 这样做了。
猜你喜欢
  • 1970-01-01
  • 2012-02-04
  • 2019-04-23
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
  • 1970-01-01
  • 1970-01-01
  • 2021-07-11
相关资源
最近更新 更多