MatJ
MatJ

Reputation: 23

How to disable set of method overloads using SFINAE?

I am writing a template class that has two parameters. This class additionally contains a method with two overloads, which accept a value of the type specified by one of the class template argument, and return a value of the type specified by the other template argument, like this:

// Type1 and Type2 are class template arguments
Type2 overloadedMethod(Type1);
Type1 overloadedMethod(Type2);

This obviously works when the template arguments are different, but becomes a problem when they are the same, as this results in two overloads with the same signature. So if the arguments are the same, I would simply like to disallow to use both overloads, so that the code may compile.

Thus far I have attempted to solve this using SFINAE and std::enable_if, but without much success. My current implementation looks like this:

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type2 overloadedMethod(Type1);
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type1 overloadedMethod(Type2);
};

and I attempt to use it like this:

// attempt to disable set of overloaded methods:
OverloadDisablingClass<int, std::string> overloadClassWithFullApi;
overloadClassWithFullApi.overloadedMethod(1);
overloadClassWithFullApi.overloadedMethod("one");
OverloadDisablingClass<std::string, std::string> overloadClassWithPartialApi;
// above line doesn't compile, overloads with same signature

Unfortunately, when I try to compile the program, gcc fails with the following error at the line where I create an instance of my class with the same template arguments:

error: 'template<class MethodType1, class MethodType2, typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> > Type1 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type2) [with MethodType1 = MethodType1; MethodType2 = MethodType2; typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> = <enumerator>; Type1 = std::__cxx11::basic_string<char>; Type2 = std::__cxx11::basic_string<char>]' cannot be overloaded
 Type1 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type2)
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: with 'template<class MethodType1, class MethodType2, typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> > Type2 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type1) [with MethodType1 = MethodType1; MethodType2 = MethodType2; typename std::enable_if<(! std::is_same<MethodType1, MethodType2>::value), bool>::type <anonymous> = <enumerator>; Type1 = std::__cxx11::basic_string<char>; Type2 = std::__cxx11::basic_string<char>]'
 Type2 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type1)
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Unless I am mistaken, both of these overloads are evaluated by the compiler, despite my attempt to disable them, and naturally, the compilation fails due to the fact that both are evaluated to the same signature.

At first, I thought that maybe the technique used to disable the methods is somehow flawed, as I am not yet that experienced when it comes to using SFINAE, so I tried a somewhat simpler example, where I have a class with a non-overloaded method which I disable depending on the class's single template argument. I am however able to compile the resulting code successfully, and the method is truly disabled if the conditions are met.

Full source code of my example used to test the behavior:

// main.cpp

#include "methoddisablingclass.hpp"
#include "overloaddisablingclass.hpp"

#include <string>

int main()
{
    // attempt to disable set of overloaded methods:
    OverloadDisablingClass<int, std::string> overloadClassWithFullApi;
    overloadClassWithFullApi.overloadedMethod(1);
    overloadClassWithFullApi.overloadedMethod("one");
    OverloadDisablingClass<std::string, std::string> overloadClassWithPartialApi;
    // above line doesn't compile, overloads with same signature

    // simpler attempt to test the SFINAE, disables a single method, works as expected
    MethodDisablingClass<std::string> simpleClassWithFullApi;
    simpleClassWithFullApi.alwaysSuitableMethod();
    simpleClassWithFullApi.sometimesUnsuitableMethod();
    MethodDisablingClass<int> simpleClassWithPartialApi;
    simpleClassWithPartialApi.alwaysSuitableMethod();
    // simpleClassWithPartialApi.sometimesUnsuitableMethod(); // <- fails to compile, as is intended

    return 0;
}
// methoddisablingclass.hpp

#ifndef METHODDISABLINGCLASS_HPP
#define METHODDISABLINGCLASS_HPP

#include <iostream>
#include <type_traits>

template <typename Type>
class MethodDisablingClass
{
  public:
    template <typename MethodType = Type, std::enable_if_t<!std::is_integral<MethodType>::value, bool> = true>
    void sometimesUnsuitableMethod();
    void alwaysSuitableMethod();
};

template <typename Type>
template <typename MethodType, std::enable_if_t<!std::is_integral<MethodType>::value, bool>>
void MethodDisablingClass<Type>::sometimesUnsuitableMethod()
{
    std::cout << __func__ << "\n";
}

template <typename Type>
void MethodDisablingClass<Type>::alwaysSuitableMethod()
{
    std::cout << __func__ << "\n";
}

#endif  // METHODDISABLINGCLASS_HPP
// overloaddisablingclass.hpp

#ifndef OVERLOADDISABLINGCLASS_HPP
#define OVERLOADDISABLINGCLASS_HPP

#include <iostream>
#include <type_traits>

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type2 overloadedMethod(Type1);
    template <typename MethodType1 = Type1, typename MethodType2 = Type2,
              std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    Type1 overloadedMethod(Type2);
};

template <typename Type1, typename Type2>
template <typename MethodType1, typename MethodType2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool>>
Type2 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type1)
{
    std::cout << __func__ << " for Type1\n";
    return Type2();
}

template <typename Type1, typename Type2>
template <typename MethodType1, typename MethodType2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool>>
Type1 OverloadDisablingClass<Type1, Type2>::overloadedMethod(Type2)
{
    std::cout << __func__ << " for Type2\n";
    return Type1();
}

#endif  // OVERLOADDISABLINGCLASS_HPP

I would therefore like to ask for help with this issue. Specifically, I would like to know why my current code doesn't work, and how can I use SFINAE to disable a set of overloaded methods within a templated class, depending on the template arguments of this class.

I need a C++14 compatible solution, but if there are possible improvements to the solution that depend on C++17 (or C++20? :-)), by all means, I am interested in them as well.

Note that after failing to do this using SFINAE, I have begun to think about using plain old template specialization, and use inheritance to avoid code duplication (The actual class for which I would like to use this is somewhat more complicated than the one in the example). But this problem has caught my interest so I would like to know the solution regardless of which approach I ultimately use.

Upvotes: 2

Views: 653

Answers (2)

super
super

Reputation: 12963

One way to approach the problem from a different angle is to specialize OverloadDisablingClass for the case where it get two identical parameters.

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    Type2 overloadedMethod(Type1);
    Type1 overloadedMethod(Type2);
};

// Implement methods here

template <typename Type1>
class OverloadDisablingClass<Type1, Type1>
{
  public:
    Type1 overloadedMethod(Type1);
};

// Implement specialization here

Upvotes: 0

CygnusX1
CygnusX1

Reputation: 21778

I think the problem with your overloads is that they do not depend on the template parameters at all. Anything that does not depend on the template parameters must be valid regardless of the substitution - even if at a later stage it gets rejected and cannot be used.

(Note, I refer to the template parameters of the methods (MethodType1 and MethodType2), not the captured template types Type1 and Type2)

A quick fix to make the signature depend on the template parameters of the method seems to work for me:

#include <type_traits>
#include <string>

template <typename Type1, typename Type2>
class OverloadDisablingClass
{
  public:
    template <typename MethodType1 = Type1, typename MethodType2 = Type2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    MethodType2 overloadedMethod(Type1) {}
    template <typename MethodType1 = Type1, typename MethodType2 = Type2, std::enable_if_t<!std::is_same<MethodType1, MethodType2>::value, bool> = true>
    MethodType1 overloadedMethod(Type2) {}
};

int main() {
    OverloadDisablingClass<int, std::string> overloadClassWithFullApi;
    overloadClassWithFullApi.overloadedMethod(1);
    overloadClassWithFullApi.overloadedMethod("one");
    OverloadDisablingClass<std::string, std::string> overloadClassWithPartialApi;
    return 0;
}

Upvotes: 1

Related Questions