13

I have a function template which takes many different types as it's input. Out of those types only one of them has a getInt() function. Hence I want the code to run the function only for that type. Please suggest a solution. Thanks

#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);
}
Konrad Rudolph
  • 482,603
  • 120
  • 884
  • 1,141
Sumit
  • 133
  • 5
  • Unrelated to your problem, but the `type_info` structure have a [equality comparison operator](https://en.cppreference.com/w/cpp/types/type_info/operator_cmp), so `typeid(T) == typeid(X)` should work as well. – Some programmer dude Feb 07 '20 at 11:18
  • 5
    Use: `if constexpr` with condition `is_same_v`. – rafix07 Feb 07 '20 at 11:19
  • The solution to this will officially become more elegant later this year with Concepts. Not super helpful right now, I know. – sweenish Feb 07 '20 at 11:20
  • There are many ways to solve your problem. A couple mentioned above. You could also use *traits* of different variants to see if a type have a callable `getInt` member. There must be quite a few questions here on stackoverflow.com alone about how to see if a structure or class have a specific member function, if you just search a little. – Some programmer dude Feb 07 '20 at 11:22
  • https://godbolt.org/z/DZyv-Q – Hui Feb 07 '20 at 11:24
  • 1
    related https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence – 463035818_is_not_a_number Feb 07 '20 at 11:29
  • @rafix07 it worked!. My question is how does constexpr help. isn't the template code generated at compile time? why only is_same itself is not enough? – Sumit Feb 07 '20 at 11:30
  • @Sumit `constexpr if` is different from a regular if and was introduced in c++17. See https://en.cppreference.com/w/cpp/language/if . – Sebastian Hoffmann Feb 07 '20 at 11:31
  • The constexpr if is needed because otherwise the compiler will evaluate the condition at compile-time, and only include `v.getInt()` if the condition is true. If you don't have a constexpr if then the compiler will try to build the `v.getInt()` call even if `v` doesn't have any `getInt` member function, which will lead to a build error. – Some programmer dude Feb 07 '20 at 11:36
  • I just discovered that I can't use c++17 – Sumit Feb 07 '20 at 12:11

3 Answers3

10

If you want to be able to call a function f for all types that have function member getInt, not just X, you can declare 2 overloads for function f:

  1. for types that have getInt member function, including class X

  2. for all the other types, including class Y.

C++11 / C++17 solution

Having that in mind, you could do something like this:

#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);
}

Check it out live.

Please note that std::void_t is introduced in C++17, but if you are limited to C++11, then it is really easy to implement void_t on your own:

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

And here is C++11 version live.

What do we have in C++20?

C++20 brings lots of good things and one of them is concepts. Above thing that's valid for C++11/C++14/C++17 can be significantly reduced in 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);
}

Check it out live.

NutCracker
  • 9,614
  • 2
  • 36
  • 62
  • Prior to C++17, that implementation of `void_t` causes issues to some old compiler though (as pointed by the link). – Jarod42 Feb 07 '20 at 11:57
  • it is not strictly necessary to write two overloads (replacing "need" with "can" would be much better imho) – 463035818_is_not_a_number Feb 07 '20 at 12:21
  • I want to ask something different, how is second function template selected for f(y). Aren't both template functions suit f(y) ? – arnes Feb 07 '20 at 12:37
  • I think Concepts is singular (as in "one of them is Concepts" or "Concepts is a new feature") – S.S. Anne Feb 07 '20 at 20:40
  • 1
    The concept definition is not accurate. you are assigning the result to an int so the concept should be `template concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to; }; ` – Hui Feb 10 '20 at 08:11
  • @Hui yes, you are correct. That would more correct. I missed that point so I will update the answer. Thanks – NutCracker Feb 10 '20 at 08:16
8

You might use if constexpr from 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
    }
    // ...
}

Before, you will have to use overloads and SFINAE or tag dispatching.

Jarod42
  • 173,454
  • 13
  • 146
  • 250
7

Keep it simple and overload. Has worked since at least C++98...

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

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

This is enough if there only ever one type with getInt function. If there's more, it's not so simple anymore. There are several ways to do it, here's one:

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.

jrok
  • 51,107
  • 8
  • 99
  • 136
  • 1
    In this case this would work since there is only one overload (for `X`), but, if there were more of similar types with member `getInt` in the future, this is not such a good practice. You probably want to note that – NutCracker Feb 07 '20 at 11:55
  • @NutCracker Did so. – jrok Feb 07 '20 at 13:05