Oleksandr Boiko
Oleksandr Boiko

Reputation: 352

Overload function/method with template argument

I want to overload a method, based only on template argument (which is a type), without passing any method arguments. For that, I use code like this (simplified, of course) in C++17:

template<typename T>
class C {
    public:
        auto f() const {
            return f((T*) nullptr);
        }
    private:
        int f(int *) const {
            return 42;
        }
        std::string f(std::string *) const {
            return "hello";
        }
        std::vector<char> f(std::vector<char> *) const {
            return {'h', 'i'};
        }
};

dump(C<int>().f());
dump(C<std::string>().f());
dump(C<std::vector<char>>().f());

, which outputs:

42

"hello"

std::vector{'h', 'i'}

I am not a professional C++ developer, and this way looks hack-ish to me. Is there a better way to overload method f without using pointers as helper arguments? Or is this a "normal" solution?

P.S.: I'm not bonded to any particular C++ version.

Upvotes: 0

Views: 95

Answers (1)

aschepler
aschepler

Reputation: 72463

(The comments on the question raise some valid questions about why you would have this. But just focusing on the question as asked.)

It is a bit hacky, but sometimes having an API function call an implementation function for technical reasons is a reasonable approach. One gotcha you might get into with that pattern is that pointers can automatically convert to other pointers: particularly T* to const T* and Derived* to Base*.

A rewrite using if constexpr (C++17 or later):

template <typename T>
T C<T>::f() const {
    if constexpr (std::is_same_v<T, int>) {
        return 42;
    } else if constexpr (std::is_same_v<T, std::string>) {
        // Note I changed the return type to T. If it's auto,
        // this would need return std::string{"hello"} so you don't
        // just return a const char*.
        return "hello";
    } else if constexpr (std::is_same_v<T, std::vector<char>>) {
        return {'h', 'i'};
    } else {
        // Always false (but don't let the compiler "know" that):
        static_assert(!std::is_same_v<T,T>,
                      "Invalid type T for C<T>::f");
    }
}

No more worries about pointer conversions, and this gives you a nicer error message if T is an unhandled type.

A rewrite using constraints (C++20 or later):

template<typename T>
class C {
    public:
        int f() const requires std::is_same_v<T,int> {
            return 42;
        }
        std::string f() const requires std::is_same_v<T,std::string> {
            return "hello";
        }
        std::vector<char> f() const
            requires std::is_same_v<T, std::vector<char>>
        {
            return {'h', 'i'};
        }
};

Now if a bad type T is used, the class doesn't contain any member named f at all! That could be better for other templates which might try checking if x.f() is a valid expression. And if a bad type is used, the compiler error might list the overloads which were declared but discarded from the class.

Upvotes: 2

Related Questions