如何使用SFINAE禁用一组方法重载?

时间:2019-04-06 08:46:27

标签: c++ templates c++14 overloading sfinae

我正在编写一个具有两个参数的模板类。该类还包含一个带有两个重载的方法,它们接受一个类模板参数指定的类型的值,并返回另一个模板参数指定的类型的值,如下所示:

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

这显然在模板参数不同时有效,但在模板参数相同时会出现问题,因为这会导致两个具有相同签名的重载。因此,如果参数相同,我只想禁止使用两个重载,以便代码可以编译。

到目前为止,我已经尝试使用SFINAE和std::enable_if解决此问题,但没有取得太大的成功。我当前的实现如下所示:

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);
};

,我尝试这样使用它:

// 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

不幸的是,当我尝试编译程序时,gcc在创建具有相同模板参数的类实例的行上失败并显示以下错误:

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)
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

除非我弄错了,尽管我试图禁用这两个重载,但编译器都会对这两个重载进行评估,并且自然地,由于两个重载都被评估为具有相同的签名,因此编译失败。

起初,我以为禁用该方法的技术可能存在某种缺陷,因为在使用SFINAE时我还没有经验,所以我尝试了一个更简单的示例,其中有一个包含我根据类的单个模板参数禁用的非重载方法。但是,我能够成功编译结果代码,并且如果满足条件,该方法将被真正禁用。

我的示例的完整源代码用于测试行为:

// 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

因此,我想寻求有关此问题的帮助。具体来说,我想知道为什么我的当前代码不起作用,以及如何使用SFINAE禁用模板化类中的一组重载方法,具体取决于此类的模板参数。

我需要一个C ++ 14兼容的解决方案,但是如果对依赖C ++ 17(或C ++ 20?:-)的解决方案有可能的改进,那么我一定会对它们感兴趣也是

请注意,在无法使用SFINAE执行此操作之后,我开始考虑使用普通的旧模板专业化,并使用继承来避免代码重复(我想为其使用的实际类比示例中的一个)。但是这个问题引起了我的兴趣,因此无论我最终使用哪种方法,我都想知道解决方案。

2 个答案:

答案 0 :(得分:1)

我认为您的重载问题在于它们根本不依赖模板参数。不依赖模板参数的任何内容都必须有效,无论替换如何-即使在以后的阶段它被拒绝并且无法使用。

(请注意,我指的是方法(MethodType1MethodType2)的模板参数,而不是捕获的模板类型Type1Type2

一种使签名取决于方法的模板参数的快速解决方案似乎对我有用:

#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;
}

答案 1 :(得分:0)

从另一个角度解决问题的一种方法是专门针对OverloadDisablingClass获得两个相同参数的情况。

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