繁体   English   中英

在 class 模板中分离参数包

[英]Divorce a parameter pack in a class template

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

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

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)成员 function 而不是doSomething(std::string x, int i, double f)成员 function。

我在 web 中找到了很多关于参数包可用性的示例,但我不知道它是否可以用于我的目的,或者我是否完全误解了参数包的用途。

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

因此,我正在寻找一种“离婚”参数包的操作。

没有专门支持此功能的“操作员”,但您的要求可以通过几种不同的方式完成,具体取决于您的要求。

从 class 模板的参数包中“提取” T类型以实现函数重载集的唯一方法是使用递归 inheritance 来实现它,其中每个实例提取一个“T”类型并实现 ZC1C125268E67A944将 rest 传递给下一个实现。

就像是:

// 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 的 function 会更容易。

为了进行精确比较,您可以创建一个is_one_of特征,仅当T恰好是其中一种类型时才确保它存在。 在 C++17 中,这可以使用std::disjunctionstd::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&) { ... }
};

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


编辑:如果你真的想变得复杂,你也可以合并这两种方法......制作一个类型特征以确定将从重载中调用的类型,并使用它来构造特定基础类型的 function 模板。

这类似于需要实现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;

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM