与类模板中的参数包离婚 [英] Divorce a parameter pack in a class template

查看:87
本文介绍了与类模板中的参数包离婚的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个使用参数包并为该参数包中包含的每种类型实现成员函数的类模板.

I am trying to write a class template that uses a parameter-pack and implements a member function for each type contained in the parameter-pack.

这是我到目前为止所拥有的:

This is what I have so far:

template <typename...T>
class Myclass {
public:
    void doSomething((Some_Operator_to_divorce?) T) {
        /*
         * Do Something
         */
        std::cout << "I did something" << std::endl;
    }
};

我的目标是拥有一个可通过以下方式使用的类模板:

My goal is to have a class template that can be used in the following way:

Myclass<std::string, int, double> M;

M.doSomething("I am a String");
M.doSomething(1234);
M.doSomething(0.1234);

类模板机制将为doSomething(std::string x)doSomething(int x)doSomething(double x)成员函数创建实现,而为不是 doSomething(std::string x, int i, double f)成员函数创建实现.

Where the class template mechanism will create an implementation for a doSomething(std::string x), a doSomething(int x) and a doSomething(double x) member function but not a doSomething(std::string x, int i, double f) member function.

我在网上找到了很多关于参数包可用性的示例,但是我不知道是否可以将其用于我的目的,或者是否完全误解了可以使用什么参数包.

I found a lot of examples in the web on the usability of parameter-packs, but I could not figure out if it can be used for my purpose, or if I totally misunderstood for what a parameter-pack can be used.

我认为我需要解压参数包,但是在阅读了许多有关解压参数包的示例之后,我认为这不是正确的选择,它具有完全不同的含义.

I thought that I need to unpack the parameter-pack but, after reading a lot of examples about unpacking parameter packs, I believe that this is not the right choice and it has a complete different meaning.

因此,因此,我正在寻找一项操作来离婚".一个参数包.

So, therefore, I am looking for a operation to "divorce" a parameter-pack.

推荐答案

没有"operator"专门为此提供支持,但您的要求可以根据您的要求以几种不同的方式完成.

There is no "operator" specifically that supports this, but what you're requesting can be done in a few different ways, depending on your requirements.

提取"唯一途径是从类模板的参数包中的T类型,其目的是实现功能的过载集,是使用递归继承来实现它,其中每个实例都提取一个"T".键入并实现该功能,然后将其余部分传递给下一个实现.

The only way to "extract" T types from a parameter pack of a class template with the purpose of implementing an overload-set of functions is to implement it using recursive inheritance, where each instance extracts one "T" type and implements the function, passing the rest on to the next implementation.

类似的东西:

// 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)类模板,其中每个模板定义每种T类型的重载.

This will instantiate sizeof...(Types) class templates, where each one defines an overload for each T type.

这可确保您获得重载语义-这样,传递int可以调用long重载,或者如果存在两个相互竞争的转换,则将是模棱两可的.

This ensures that you get overload semantics -- such that passing an int can call a long overload, or will be ambiguous if there are two competing conversions.

但是,如果不必要,那么使用enable_if和条件使用SFINAE启用该功能会更容易.

However, if this is not necessary, then it'd be easier to enable the function with SFINAE using enable_if and a condition.

对于精确比较,您可以创建一个is_one_of特征,只有在T恰好是其中一种类型时,才能确保该特征存在.在C ++ 17中,可以使用std::disjunctionstd::is_same:

For exact comparisons, you could create an is_one_of trait that only ensures this exists if T is exactly one of the types. In C++17, this could be done with std::disjunction and 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>...>{};

或者,您可能希望此方法仅在它与可转换类型一起使用时才起作用-您可以执行以下操作:

Alternatively, you may want this to only work if it may work with convertible types -- which you might do something like:

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)

The difference between the two is that if you passed a string literal to a MyClass<std::string>, it will work with the second option since it's convertible, but not the first option since it's exact. The deduced T type from the template will also be different, with the former being exactly one of Types..., and the latter being convertible (again, T may be const char*, but Types... may only contain std::string)

要将其一起用于您的MyClass模板,只需使用enable_if通过SFINAE启用条件:

To work this together into your MyClass template, you just need to enable the condition with SFINAE using 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&) { ... }
};

哪种解决方案适合您,完全取决于您的要求(重载语义,精确调用约定或转换调用约定)

Which solution works for you depends entirely on your requirements (overload semantics, exact calling convension, or conversion calling convension)

:如果您真的想变得复杂,您还可以将两种方法合并起来...制作类型特征以确定从一个对象中调用什么类型重载,并使用它来构建特定基础类型的功能模板.

if you really wanted to get complex, you can also merge the two approaches... Make a type trait to determine what type would be called from an overload, and use this to construct a function template of a specific underlying type.

这与需要实现variant的方式类似,因为它具有U构造函数,该构造函数将所有类型都视为过载集:

This is similar to how variant needs to be implemented, since it has a U constructor that considers all types as an overload set:

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

然后将其与具有功能模板的第二种方法一起使用:

And then use this with the second approach of having a function template:

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条件来确保可以将其转换,然后确定将其视为适当的类型并将其委派给实际的(私有)实现.

This last approach does it two-phase; it uses the first SFINAE condition to ensure it can be converted, and then determines the appropriate type to treat it as and delegates it to the real (private) implementation.

这要复杂得多,但是可以实现类似重载的语义,而实际上并不需要在创建它的类型中进行递归实现.

This is much more complex, but can achieve the overload-like semantics without actually requiring recursive implementation in the type creating it.

这篇关于与类模板中的参数包离婚的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆