仅针对定义了函数的类型在函数模板内执行函数

Sum*_*mit 13 c++ templates metaprogramming c++11 c++14

我有一个函数模板,它接受许多不同的类型作为输入。在这些类型中,只有其中一种具有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);
}
Run Code Online (Sandbox Code Playgroud)

Nut*_*ker 10

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

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

  2. 对于所有其他类型,包括 class 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);
}
Run Code Online (Sandbox Code Playgroud)

现场查看一下。

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

template <typename...>
using void_t = void;
Run Code Online (Sandbox Code Playgroud)

这是 C++11 版本的实时版本。

C++20 有什么?

C++20 带来了很多好东西,其中之一就是概念。上述对 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);
}
Run Code Online (Sandbox Code Playgroud)

现场查看一下。


Jar*_*d42 8

if constexpr您可以从 C++17使用:

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
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

在此之前,您必须使用重载和 SFINAE 或标签调度。


jro*_*rok 7

保持简单和过载。至少从 C++98 开始就可以工作...

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

void f(X& v)
{
    int result = v.getInt();
}
Run Code Online (Sandbox Code Playgroud)

如果只有一种具有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
}
Run Code Online (Sandbox Code Playgroud)

带有诊断输出的实例。