Any potential problem with using std::is_same_v rather than overload or specialization?

Viewed 200

For example, I've got this:

// Code A
int create_int() { return 42; }
double create_double() { return 3.14; }
std::string create_string() { return {"Hi"}; }

Now let's assume that it makes sense to put these creates together, so I rewrite the code:

// Code B
template <typename T> T create();
template <> int create<int>() { return 42; }
template <> double create<double>() { return 3.14; }
template <> std::string create<std::string>() { return {"Hi"}; }

Or even:

// Code C
#include <type_traits>

template <typename T> T create()
{
    if constexpr (std::is_same_v<T, int>)
    {
        return 42;
    }
    else if constexpr (std::is_same_v<T, double>)
    {
        return 3.14;
    }
    else if constexpr (std::is_same_v<T, std::string>)
    {
        return {"Hi"};
    }
    else
    {
        // static_assert(false);
        return static_cast<T>(0);
    }
}

I wanna to know is there any difference between these codes, or is it just about code style.

2 Answers

There are semantic differences between these. You can't use the Code A functions in generic algorithms like in this example:

template <class T>
T generic_function() {
    return create<T>();
}

Therefore I would prefere Code B over Code A.

The constexpr if is useful if you need to take a different route in generic algorithms. It saves you from creating an overloaded helper function or worse constructs.

An example would be to take a different route for void over other datatypes because you can not pass void as a parameter to a function. Suppose you take a function and want to set the value of a std::promise to the result. This function might not return a value, but you still want to execute the function. In this case the constexpr if will save you from a lot of headache and template metaprogramming.

template <class Fn>
void my_function(Fn fn) {
    std::promise<decltype(fn())> promise;

    if constexpr(!std::is_same_v<void, decltype(fn())>) {
        promise.set_value(fn());
    } else {
        fn();
        promise.set_value();
    }
}

It depends on your use case. if constexpr is limited to function scope, meaning you can't define your create function for Foo in foo.h and Bar in bar.h, so you have to include both.

This is not a criticism of if constexpr, as it is incredible useful in keeping related generic code brief and in one place.

Related