nakiya
nakiya

Reputation: 14403

What is wrong with this use of std::enable_if?

I have a function set_data which I have to implement in different ways for different types it takes in. For example, this is trying to implement two overloads based on input type. If it is fundamental, non void and non nullptr_t, then handle it in first implementation. If it is a std::string or a char buffer, handle it second way.

struct field
{
    template<typename TDataType, typename=void>
    void set_data(TDataType data, size_t index = 0);
};

template<typename TDataType, typename = typename
        std::enable_if< std::is_fundamental<TDataType>::value &&
                        std::is_same<TDataType, nullptr_t>::value == false &&
                        std::is_same<TDataType, void>::value == false>::type>
void field::set_data(TDataType data, size_t index /* = 0 */)
{
}

template<typename TDataType, typename = typename
        std::enable_if< std::is_same<std::string const &, TDataType> ||
                        std::is_same<char const *, TDataType>>::type>
void field::set_data(TDataType data, size_t index /* = 0 */)
{
}

Then I call like :

field f;
int x = 10;
f.set_data(x);

And the compiler throws an error at me.

defs.h(111): error C2995: 'void messaging::field::set_data(TDataType,size_t)' : function template has already been defined

How do I resolve this?

On Visual studio 2013

Upvotes: 6

Views: 2538

Answers (3)

You're trying to define the overloads outside of the class, which is of course not allowed. Or you're maybe trying to provide two partial specialisations for the member function template, but function templates cannot be partially specialised.

You need to get rid of the common declaration and move the two overloads into the class. You have to make them distinguishable, however. You can do that by giving one of them an extra template parameter (and add some missing ::value to the second overload):

struct field
{
    template<typename TDataType, typename = typename
            std::enable_if< std::is_fundamental<TDataType>::value &&
                            std::is_same<TDataType, std::nullptr_t>::value == false &&
                            std::is_same<TDataType, void>::value == false>::type>
    void set_data(TDataType data, size_t index = 0)
    {
    }

    template<typename TDataType, typename = void, typename = typename
            std::enable_if< std::is_same<std::string const &, TDataType>::value ||
                            std::is_same<char const *, TDataType>::value>::type>
    void set_data(TDataType data, size_t index = 0)
    {
    }
};

Live example

Additionally, note that if the function template's argument is pass-by-value, the template argument can never be deduced to a reference type. So is_same<std::string const &, TDataType> can only ever be true if the template argument std::string const & is specified explicitly on the call site. You might want to replace it with just checking for std::string. It might even be worthwhile to throw in some std::remove_reference and std::remove_cv to correctly handle explicitly specified template arguments.

Upvotes: 3

Tristan Brindle
Tristan Brindle

Reputation: 16824

Using SFINAE on the template arguments like this doesn't work, thought I'm not 100% sure why (EDIT: Agnew's answer explains that it's because the templates are then indistinguishable). If you instead use std::enable_if on the return type, and move the definitions in-class, then it works (tested with gcc and clang):

struct field
{
    template<typename TDataType>
    void set_data(TDataType data, size_t index = 0) {}

    template<typename TDataType>
    typename std::enable_if<std::is_fundamental<TDataType>::value &&
                            !std::is_same<TDataType, std::nullptr_t>::value &&
                            !std::is_same<TDataType, void>::value,
                            void>::type
    set_data(TDataType data, size_t index /* = 0 */)
    {
    }

    template<typename TDataType>
    typename std::enable_if<std::is_same<std::string const &, TDataType>::value ||
                            std::is_same<char const *, TDataType>::value,
                            void>::type
    set_data(TDataType data, size_t index /* = 0 */)
    {
    }

};

(I've also fixed up a couple of other bugs, namely not asking for the value member of std::is_same int the second overload, and not specifying the namespace for std::nullptr_t).

Upvotes: 3

Walter
Walter

Reputation: 45414

The usual use of enable_if for SFINAE on (member) functions (as in your example) is on the return type (rather than an additional template parameter). This would give you

struct field
{
    template<typename TDataType>
    typename std::enable_if< std::is_fundamental<TDataType>::value &&
                            !std::is_same<TDataType, std::nullptr_t>::value &&
                            !std::is_same<TDataType, void>::value>::
    type field::set_data(TDataType data, size_t index = 0)
    {
    }

    template<typename TDataType>
    typename std::enable_if< std::is_same<std::string const&, TDataType>::value ||
                             std::is_same<char const*, TDataType>::value>::
    type field::set_data(TDataType data, size_t index = 0)
    {
    }
};

Upvotes: 3

Related Questions