user4385532
user4385532

Reputation:

With templates, how to differentiate two parallel cases, such as between floating point types and integral types?

cppreference.com ( http://en.cppreference.com/w/cpp/types/enable_if#Notes ) notes that:

A common mistake is to declare two function templates that differ only in their default template arguments. This is illegal because default template arguments are not part of function template's signature, and declaring two different function templates with the same signature is illegal.

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              typename = std::enable_if_t<std::is_integral<Integer>::value>
    >
    T(Integer) : m_type(int_t) {}
 
    template <typename Floating,
              typename = std::enable_if_t<std::is_floating_point<Floating>::value>
    >
    T(Floating) : m_type(float_t) {} // error: cannot overload
};

So true… So what is the correct approach to solve this issue and actually achieve what the above incorrect code fails to achieve?

Upvotes: 6

Views: 290

Answers (4)

user4385532
user4385532

Reputation:

I believe I have found a yet another way to sort this out. This sadly won’t work with floats due to the limitations of the use of floating point types within templates, but should work for most other cases nevertheless (such as differentiating between signed and unsigned types):

struct T {
    enum { signed_t,unsigned_t } m_type;
    template <typename Signed,
              std::enable_if_t<std::is_signed<Signed>::value, bool> = true
    >
    T(Signed) : m_type(signed_t) {}

    template <typename Unsigned,
              std::enable_if_t<std::is_unsigned<Unsigned>::value, bool> = true
    >
    T(Unsigned) : m_type(unsigned_t) {}
};

Live example: http://ideone.com/xqp4nd

Upvotes: 0

W.F.
W.F.

Reputation: 13988

I believe this would work:

#include <type_traits>

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              std::enable_if_t<std::is_integral<Integer>::value>* = nullptr
    >
    T(Integer) : m_type(int_t) {}

    template <typename Floating,
              std::enable_if_t<std::is_floating_point<Floating>::value>* = nullptr
    >
    T(Floating) : m_type(float_t) {} // now the overload is valid
};

int main() {
    T t = int{};
    T t2 = float{};
    (void)t;
    (void)t2;
}

[live demo]

Upvotes: 5

jrok
jrok

Reputation: 55415

Use enable_if in another dummy argument in constructor's parameter list:

struct T {
    enum { int_t,float_t } m_type;

    template <typename Integer>
    T(Integer, std::enable_if_t<std::is_integral<Integer>::value>* = nullptr)
        : m_type(int_t) {}

    template <typename Floating>
    T(Floating, std::enable_if_t<std::is_floating_point<Floating>::value>* = nullptr)
        : m_type(float_t) {}
};

Upvotes: 1

krzaq
krzaq

Reputation: 16431

Use tag dispatching:

namespace tag
{
struct floating{};
struct integer{};
struct error{};

template<typename T, typename = void> struct get : error {};

template<typename T>
struct get<T, std::enable_if_t<std::is_integral<T>::value>> : integer {};

template<typename T>
struct get<T, std::enable_if_t<std::is_floating_point<T>::value>> : floating {};

}

struct Foo
{
    template<typename T>
    Foo(T&&, tag::floating){
    }

    template<typename T>
    Foo(T&&, tag::integer){
    }

    template<typename T>
    Foo(T&& t) : Foo(std::forward<T>(t), tag::get<std::decay_t<T>>{}) {}
};

demo

Upvotes: 4

Related Questions