Execute function inside function template only for those types that have the function defined
Asked Answered
R

3

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);
}
Riesman answered 7/2, 2020 at 11:14 Comment(10)
Unrelated to your problem, but the type_info structure have a equality comparison operator, so typeid(T) == typeid(X) should work as well.Naval
Use: if constexpr with condition is_same_v<T,X>.Reprove
The solution to this will officially become more elegant later this year with Concepts. Not super helpful right now, I know.Atlantis
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.Naval
godbolt.org/z/DZyv-QCrafton
related #257788Oversweet
@Reprove 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?Riesman
@Sumit constexpr if is different from a regular if and was introduced in c++17. See en.cppreference.com/w/cpp/language/if .Remsen
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.Naval
I just discovered that I can't use c++17Riesman
A
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.

Absorbent answered 7/2, 2020 at 11:34 Comment(6)
Prior to C++17, that implementation of void_t causes issues to some old compiler though (as pointed by the link).Pimple
it is not strictly necessary to write two overloads (replacing "need" with "can" would be much better imho)Oversweet
I want to ask something different, how is second function template selected for f(y). Aren't both template functions suit f(y) ?Hale
I think Concepts is singular (as in "one of them is Concepts" or "Concepts is a new feature")Genesisgenet
The concept definition is not accurate. you are assigning the result to an int so the concept should be template<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; }; Crafton
@Crafton yes, you are correct. That would more correct. I missed that point so I will update the answer. ThanksAbsorbent
P
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.

Pimple answered 7/2, 2020 at 11:35 Comment(3)
if constexpr is a C++17 feature.Peer
@NutCracker: Not nice to update tag/question and so invalidating existing answers... (even if warning about that is fine).Pimple
i just updated tag... question title was updated by OPAbsorbent
B
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.

Bluestone answered 7/2, 2020 at 11:51 Comment(2)
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 thatAbsorbent
@Absorbent Did so.Bluestone

© 2022 - 2024 — McMap. All rights reserved.