Home > OS >  Using enable_if to hide member function according to template type
Using enable_if to hide member function according to template type

Time:11-16

I want my class template to provide an extra function member if the template parameter matches a specific type. I am trying to use std::enable_if to get a SFINAE implementation, but I am struggling to find the correct syntax for this. I've tried several solutions for similar questions, but for some reason none of them compile.

#include <type_traits>
#include <string>

template < typename T >
class myClass {
 public:
    using value_type = T;
    using other_type = int;

    myClass() = default;
    virtual ~myClass() = default;

    // 'default' member
    void function(const value_type& val) {};

    // overload #1
    // error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
    template < typename = typename std::enable_if< !std::is_convertible< other_type, value_type >::value >::type >
    void function(const other_type& val) {};

    // overload #2
    // error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
    template < std::enable_if_t< !std::is_convertible< other_type, value_type >::value >* = nullptr >
    void function(const other_type& val) {};
};



int main(int argc, char const *argv[]) {
    myClass< std::string > foo;  // OK
    myClass< float > bar;            // error: no type named ‘type’ in ‘struct std::enable_if<false, void>’

    return 0;
}

I want function to be available for all types, but its overload to only be available when other_type is not implicitly convertible to value_type. How would I go about implementing this?

CodePudding user response:

For SFINAE to apply, you need the thing you are trying to use SFINAE on to have it's own template argument, and you need to use that template argument in the condition. In this case, you could fix it by adding a template argument typename V = value_type to the template arguments and using V instead of value_type. Now the condition would depend on V which is a template argument of the member function :

#include <type_traits>
#include <string>

template < typename T >
class myClass {
 public:
    using value_type = T;
    using other_type = int;

    myClass() = default;
    virtual ~myClass() = default;

    // 'default' member
    void function(const value_type& val) {};

    // overload #1
    // error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
    template < typename V = value_type, typename = typename std::enable_if< !std::is_convertible< other_type, V >::value >::type >
    //   added ^^^^^^^^^^^^^^^^^^^^^^^^                                               changed value_type to V ^
    void function(const other_type& val) {};

    // overload #2
    // error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
    template < typename V = value_type, std::enable_if_t< !std::is_convertible< other_type, V >::value >* = nullptr >
    //   added ^^^^^^^^^^^^^^^^^^^^^^^^                             changed value_type to V ^
    void function(const other_type& val) {};
};



int main(int argc, char const *argv[]) {
    myClass< std::string > foo;  // OK
    myClass< float > bar;        // OK

    return 0;
}

CodePudding user response:

In the function member function template overloads, the SFINAE constructs (std::enable_if_t) contains no dependent type, meaning there's no substitutation context, and the failures in the template-head's of the overloads are not substitutation failures, they're hard errors. Recall:

  • SFINAE - Substitutation Failure Is Not An Error

You typically solve this by adding a dummy template parameter whose default-template-argument is that of the enclosing class template's template parameter which you originally used directly (T; your value type):

template <typename U = T, typename = typename std::enable_if<
                              !std::is_convertible<other_type, U>::value>::type>
void function(const other_type &val){};

template <
    typename U = T,
    std::enable_if_t<!std::is_convertible<other_type, U>::value> * = nullptr>
void function(const other_type &val){};

The std::enable_if_t SFINAE constructs are now dependent in the context of each member function template overload.

CodePudding user response:

Since the introduction of concepts in C 20, most of SFINAE machinaries can be simplified or at least expressed less verbosely.

#include <concepts>
#include <iostream>
#include <string>

template < class Type >
class myClass {
 public:
    myClass() = default;

    void function(Type const& val) {
        std::cout << "Default: " << val << "\n";
    }

    template < class Other >
        requires (not std::convertible_to<Other, Type>)  // <- Constrain
    void function(Other const& val) {
        std::cout << "Overload: " << val << "\n";
    }
};



int main()
{
    myClass< std::string > foo;
    foo.function("OK");          // Default
    foo.function(42);            // Overload

    myClass< float > bar;
    bar.function(42);            // Default
    bar.function("OK");          // Overload
    bar.function(3.14);          // Default
}
  • Related