如何使用 SFINAE 禁用方法重载集?
How to disable set of method overloads using SFINAE?
我正在编写一个模板 class,它有两个参数。此 class 还包含一个具有两个重载的方法,它接受由 class 模板参数之一指定的类型的值,以及 return 由另一个模板指定的类型的值参数,像这样:
// 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 失败并在我使用相同模板参数创建 class 实例的那一行出现以下错误:
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 方面还没有那么丰富的经验,所以我尝试了一个更简单的示例,其中我有一个 class 使用非重载方法,我根据 class 的单个模板参数禁用了该方法。然而,我能够成功编译生成的代码,如果满足条件,该方法将真正被禁用。
用于测试行为的示例的完整源代码:
// 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 禁用模板化 class 中的一组重载方法,具体取决于此 class 的模板参数.
我需要一个 C++14 兼容的解决方案,但是如果有可能改进依赖于 C++17(或 C++20?:-) 的解决方案,无论如何,我很感兴趣在他们中也是如此。
请注意,在使用 SFINAE 未能做到这一点后,我开始考虑使用普通的旧模板专业化,并使用继承来避免代码重复(我想使用这个的实际 class比示例中的要复杂一些)。但是这个问题引起了我的兴趣,所以无论我最终使用哪种方法,我都想知道解决方案。
我认为您的重载的问题在于它们根本不依赖于模板参数。无论替换如何,任何不依赖于模板参数的内容都必须有效 - 即使在稍后阶段它被拒绝并且无法使用。
(注意,我指的是方法的模板参数(MethodType1
和MethodType2
),而不是捕获的模板类型Type1
和Type2
)
使签名依赖于方法的模板参数的快速修复似乎对我有用:
#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;
}
从不同角度解决问题的一种方法是专门针对 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
我正在编写一个模板 class,它有两个参数。此 class 还包含一个具有两个重载的方法,它接受由 class 模板参数之一指定的类型的值,以及 return 由另一个模板指定的类型的值参数,像这样:
// 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 失败并在我使用相同模板参数创建 class 实例的那一行出现以下错误:
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 方面还没有那么丰富的经验,所以我尝试了一个更简单的示例,其中我有一个 class 使用非重载方法,我根据 class 的单个模板参数禁用了该方法。然而,我能够成功编译生成的代码,如果满足条件,该方法将真正被禁用。
用于测试行为的示例的完整源代码:
// 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 禁用模板化 class 中的一组重载方法,具体取决于此 class 的模板参数.
我需要一个 C++14 兼容的解决方案,但是如果有可能改进依赖于 C++17(或 C++20?:-) 的解决方案,无论如何,我很感兴趣在他们中也是如此。
请注意,在使用 SFINAE 未能做到这一点后,我开始考虑使用普通的旧模板专业化,并使用继承来避免代码重复(我想使用这个的实际 class比示例中的要复杂一些)。但是这个问题引起了我的兴趣,所以无论我最终使用哪种方法,我都想知道解决方案。
我认为您的重载的问题在于它们根本不依赖于模板参数。无论替换如何,任何不依赖于模板参数的内容都必须有效 - 即使在稍后阶段它被拒绝并且无法使用。
(注意,我指的是方法的模板参数(MethodType1
和MethodType2
),而不是捕获的模板类型Type1
和Type2
)
使签名依赖于方法的模板参数的快速修复似乎对我有用:
#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;
}
从不同角度解决问题的一种方法是专门针对 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