在 class 模板中分离参数包
Divorce a parameter pack in a class template
我正在尝试编写一个 class 模板,该模板使用参数包并为参数包中包含的每种类型实现一个成员函数。
这是我目前拥有的:
template <typename...T>
class Myclass {
public:
void doSomething((Some_Operator_to_divorce?) T) {
/*
* Do Something
*/
std::cout << "I did something" << std::endl;
}
};
我的目标是拥有一个可以按以下方式使用的 class 模板:
Myclass<std::string, int, double> M;
M.doSomething("I am a String");
M.doSomething(1234);
M.doSomething(0.1234);
其中 class 模板机制将为 doSomething(std::string x)
、doSomething(int x)
和 doSomething(double x)
成员函数创建实现,但 不会 一个doSomething(std::string x, int i, double f)
成员函数。
我在网上找到了很多关于参数包可用性的例子,但是我不知道它是否可以用于我的目的,或者我是否完全误解了参数包的含义使用过。
我以为我需要解压参数包,但是在阅读了很多关于解压参数包的例子后,我认为这不是正确的选择,它具有完全不同的含义。
所以,因此,我正在寻找一个“分离”参数包的操作。
没有专门支持此功能的“运算符”,但您的请求可以通过几种不同的方式完成,具体取决于您的要求。
从 class 模板的参数包中“提取”T
类型的唯一方法 目的是实现函数的重载集 是使用递归继承来实现它,其中每个实例提取一个“T”类型并实现该功能,将其余部分传递给下一个实现。
类似于:
// Extract first 'T', pass on 'Rest' to next type
template <typename T, typename...Rest>
class MyClassImpl : public MyClassImpl<Rest...>
{
public:
void doSomething(const T&) { ... }
using MyClassImpl<Rest...>::doSomething;
};
template <typename T>
class MyClassImpl<T> // end-case, no more 'Rest'
{
public:
void doSomething(const T&) { ... }
};
template <typename...Types>
class MyClass : public MyClassImpl<Types...>
{
public:
using MyClassImpl<Types...>::doSomething;
...
};
这将实例化 sizeof...(Types)
class 模板,其中每个模板为每个 T
类型定义一个重载。
这确保您获得重载语义——这样传递一个 int
可以调用一个 long
重载,或者如果有两个竞争转换将是不明确的。
但是,如果这不是必需的,那么使用 enable_if
和条件使用 SFINAE 启用该功能会更容易。
为了进行精确比较,您可以创建一个 is_one_of
特征,它仅在 T
恰好是其中一种类型时才确保它存在。在 C++17 中,这可以通过 std::disjunction
和 std::is_same
:
来完成
#include <type_traits>
// A trait to check that T is one of 'Types...'
template <typename T, typename...Types>
struct is_one_of : std::disjunction<std::is_same<T,Types>...>{};
或者,您可能希望它仅在适用于可转换类型时才有效——您可以这样做:
template <typename T, typename...Types>
struct is_convertible_to_one_of : std::disjunction<std::is_convertible<T,Types>...>{};
两者之间的区别在于,如果您将字符串文字传递给 MyClass<std::string>
,它将使用第二个选项,因为它是可转换的,但不是第一个选项,因为它是精确的。从模板中推导出的 T
类型也会有所不同,前者恰好是 Types...
之一,而后者是可转换的(同样,T
可能是 const char*
,但 Types...
可能只包含 std::string
)
要将其整合到您的 MyClass
模板中,您只需使用 enable_if
:
启用 SFINAE 条件
template <typename...Types>
class MyClass
{
public:
// only instantiates if 'T' is exactly one of 'Types...'
template <typename T, typename = std::enable_if_t<is_one_of<T, Types...>::value>>
void doSomething(const T&) { ... }
// or
// only instantiate if T is convertible to one of 'Types...'
template <typename T, typename = std::enable_if_t<is_convertible_to_one_of<T, Types...>::value>>
void doSomething(const T&) { ... }
};
哪种解决方案适合您完全取决于您的要求(重载语义、精确调用约定或转换调用约定)
编辑:如果你真的想要变得复杂,你也可以合并这两种方法......制作一个类型特征确定将从重载中调用什么类型,并使用它来构造特定基础类型的函数模板。
这类似于 variant
的实现方式,因为它有一个 U
构造函数,将所有类型视为重载集:
// create an overload set of all functions, and return a unique index for
// each return type
template <std::size_t I, typename...Types>
struct overload_set_impl;
template <std::size_t I, typename T0, typename...Types>
struct overload_set_impl<I,T0,Types...>
: overload_set_impl<I+1,Types...>
{
using overload_set_impl<I+1,Types...>::operator();
std::integral_constant<std::size_t,I> operator()(T0);
};
template <typename...Types>
struct overload_set : overload_set_impl<0,Types...> {};
// get the index that would be returned from invoking all overloads with a T
template <typename T, typename...Types>
struct index_of_overload : decltype(std::declval<overload_set<Types...>>()(std::declval<T>())){};
// Get the element from the above test
template <typename T, typename...Types>
struct constructible_overload
: std::tuple_element<index_of_overload<T, Types...>::value, std::tuple<Types...>>{};
template <typename T, typename...Types>
using constructible_overload_t
= typename constructible_overload<T, Types...>::type;
然后将其与具有函数模板的第二种方法一起使用:
template <typename...Types>
class MyClass {
public:
// still accept any type that is convertible
template <typename T, typename = std::enable_if_t<is_convertible_to_one_of<T, Types...>::value>>
void doSomething(const T& v)
{
// converts to the specific overloaded type, and call it
using type = constructible_overload_t<T, Types...>;
doSomethingImpl<type>(v);
}
private:
template <typename T>
void doSomethingImpl(const T&) { ... }
最后一种方法是两阶段的;它使用第一个 SFINAE 条件来确保它可以转换,然后确定适当的类型将其视为并将其委托给真正的(私有)实现。
这要复杂得多,但可以实现类似重载的语义,而实际上不需要在创建它的类型中进行递归实现。
我正在尝试编写一个 class 模板,该模板使用参数包并为参数包中包含的每种类型实现一个成员函数。
这是我目前拥有的:
template <typename...T>
class Myclass {
public:
void doSomething((Some_Operator_to_divorce?) T) {
/*
* Do Something
*/
std::cout << "I did something" << std::endl;
}
};
我的目标是拥有一个可以按以下方式使用的 class 模板:
Myclass<std::string, int, double> M;
M.doSomething("I am a String");
M.doSomething(1234);
M.doSomething(0.1234);
其中 class 模板机制将为 doSomething(std::string x)
、doSomething(int x)
和 doSomething(double x)
成员函数创建实现,但 不会 一个doSomething(std::string x, int i, double f)
成员函数。
我在网上找到了很多关于参数包可用性的例子,但是我不知道它是否可以用于我的目的,或者我是否完全误解了参数包的含义使用过。
我以为我需要解压参数包,但是在阅读了很多关于解压参数包的例子后,我认为这不是正确的选择,它具有完全不同的含义。
所以,因此,我正在寻找一个“分离”参数包的操作。
没有专门支持此功能的“运算符”,但您的请求可以通过几种不同的方式完成,具体取决于您的要求。
从 class 模板的参数包中“提取”T
类型的唯一方法 目的是实现函数的重载集 是使用递归继承来实现它,其中每个实例提取一个“T”类型并实现该功能,将其余部分传递给下一个实现。
类似于:
// Extract first 'T', pass on 'Rest' to next type
template <typename T, typename...Rest>
class MyClassImpl : public MyClassImpl<Rest...>
{
public:
void doSomething(const T&) { ... }
using MyClassImpl<Rest...>::doSomething;
};
template <typename T>
class MyClassImpl<T> // end-case, no more 'Rest'
{
public:
void doSomething(const T&) { ... }
};
template <typename...Types>
class MyClass : public MyClassImpl<Types...>
{
public:
using MyClassImpl<Types...>::doSomething;
...
};
这将实例化 sizeof...(Types)
class 模板,其中每个模板为每个 T
类型定义一个重载。
这确保您获得重载语义——这样传递一个 int
可以调用一个 long
重载,或者如果有两个竞争转换将是不明确的。
但是,如果这不是必需的,那么使用 enable_if
和条件使用 SFINAE 启用该功能会更容易。
为了进行精确比较,您可以创建一个 is_one_of
特征,它仅在 T
恰好是其中一种类型时才确保它存在。在 C++17 中,这可以通过 std::disjunction
和 std::is_same
:
#include <type_traits>
// A trait to check that T is one of 'Types...'
template <typename T, typename...Types>
struct is_one_of : std::disjunction<std::is_same<T,Types>...>{};
或者,您可能希望它仅在适用于可转换类型时才有效——您可以这样做:
template <typename T, typename...Types>
struct is_convertible_to_one_of : std::disjunction<std::is_convertible<T,Types>...>{};
两者之间的区别在于,如果您将字符串文字传递给 MyClass<std::string>
,它将使用第二个选项,因为它是可转换的,但不是第一个选项,因为它是精确的。从模板中推导出的 T
类型也会有所不同,前者恰好是 Types...
之一,而后者是可转换的(同样,T
可能是 const char*
,但 Types...
可能只包含 std::string
)
要将其整合到您的 MyClass
模板中,您只需使用 enable_if
:
template <typename...Types>
class MyClass
{
public:
// only instantiates if 'T' is exactly one of 'Types...'
template <typename T, typename = std::enable_if_t<is_one_of<T, Types...>::value>>
void doSomething(const T&) { ... }
// or
// only instantiate if T is convertible to one of 'Types...'
template <typename T, typename = std::enable_if_t<is_convertible_to_one_of<T, Types...>::value>>
void doSomething(const T&) { ... }
};
哪种解决方案适合您完全取决于您的要求(重载语义、精确调用约定或转换调用约定)
编辑:如果你真的想要变得复杂,你也可以合并这两种方法......制作一个类型特征确定将从重载中调用什么类型,并使用它来构造特定基础类型的函数模板。
这类似于 variant
的实现方式,因为它有一个 U
构造函数,将所有类型视为重载集:
// create an overload set of all functions, and return a unique index for
// each return type
template <std::size_t I, typename...Types>
struct overload_set_impl;
template <std::size_t I, typename T0, typename...Types>
struct overload_set_impl<I,T0,Types...>
: overload_set_impl<I+1,Types...>
{
using overload_set_impl<I+1,Types...>::operator();
std::integral_constant<std::size_t,I> operator()(T0);
};
template <typename...Types>
struct overload_set : overload_set_impl<0,Types...> {};
// get the index that would be returned from invoking all overloads with a T
template <typename T, typename...Types>
struct index_of_overload : decltype(std::declval<overload_set<Types...>>()(std::declval<T>())){};
// Get the element from the above test
template <typename T, typename...Types>
struct constructible_overload
: std::tuple_element<index_of_overload<T, Types...>::value, std::tuple<Types...>>{};
template <typename T, typename...Types>
using constructible_overload_t
= typename constructible_overload<T, Types...>::type;
然后将其与具有函数模板的第二种方法一起使用:
template <typename...Types>
class MyClass {
public:
// still accept any type that is convertible
template <typename T, typename = std::enable_if_t<is_convertible_to_one_of<T, Types...>::value>>
void doSomething(const T& v)
{
// converts to the specific overloaded type, and call it
using type = constructible_overload_t<T, Types...>;
doSomethingImpl<type>(v);
}
private:
template <typename T>
void doSomethingImpl(const T&) { ... }
最后一种方法是两阶段的;它使用第一个 SFINAE 条件来确保它可以转换,然后确定适当的类型将其视为并将其委托给真正的(私有)实现。
这要复杂得多,但可以实现类似重载的语义,而实际上不需要在创建它的类型中进行递归实现。