【问题标题】:Trying to require multiple variadic types to be specific types试图要求多个可变参数类型成为特定类型
【发布时间】:2020-06-14 19:09:56
【问题描述】:

这目前是伪代码,因为这是我在开始将其编写为完整代码之前正在研究的一个想法。

我知道我可以创建一个使用va_arg 和va_list 的普通可变参数函数,例如printf(),但是我想完全避免使用它们。

我正在考虑改用模板化可变参数。我正在考虑使用可变参数制作一个可实例化的类模板。这里的条件是这个类的构造函数只能接受两种类型,但任何一种类型的数量都可以变化。我知道与编译器将如何解释以及调用它们的顺序相比,参数在代码中的写入位置是不可知的,但这不是问题。我为参数选择的顺序是出于可读性和一致性目的的约定。

这是一个伪代码示例:

class TypeIn {...}
class TypeOut{...}

template<typename... T1, typename... T2>
class MyObject {
    std::array<TypeIn*> inputs_;
    std::array<TypeOut*> outputs_;
public:
    MyObject(T1&&... inputs, T2&& ... outputs) { ... }
};

由于我仍在使用 C++17 并且还没有 C++20 的概念、模块和协程,那么确保T1是TypeIn 和T2 是TypeOut 类对象并相应地填充数组?我可以使用向量,但是一旦构造了对象,输入和输出的大小就不会改变。

可能的用例是:

using In = TypeIn;
using Out = TypeOut;
MyObject obj( In a, In b, In c, Out x, Out y);

如果可能的话,我宁愿不使用这个语法:

MyObject<In,In,In,Out,Out> obj( In a, In b, In c, Out X, Out y);

因为第一个更清晰或更易读。



编辑

经过一番思考后,我想知道这是否可行……

class In {...}
class Out{...}


// template<typename T1 = In, typename T2 = Out>
// T1 must == type In and T2 must == type Out
class MyObject {
private:
     std::vector<In*> inputs_;
     std::vector<Out*> outputs_;

public:
      MyObject() = deafault;

      template<typename... Inputs> // would probably use move semantics or forwarding
      void assignInputs(Inputs&& ... inputs);

      template<typename... Outputs> // would probably use move semantics or forwarding
      void assignOutputs(Inputs&& ... outputs);
};

但是,这将迫使用户必须构造对象,然后调用这两个函数......我试图在构造时完成所有这些......

【问题讨论】:

  • 有许多可能的方法,基于 SFINAE 或 static_asserts。哪种方法更适合您?
  • @SamVarshavchik 我明白这一点,但这就是我被困在决策树上的地方......所以很高兴看到一些不同方法的例子,然后我可以根据我的决定从那里采取哪条路线......
  • 你真的希望你的构造函数只接受右值引用吗? (仅当函数本身是模板时才会发生转发引用)
  • MyObject(std::array&lt;In, N1&gt; inputs, std::array&lt;Out, N2&gt; outputs) 有什么问题?
  • @FrancisCugler 那仍然是在编译时。不过,您需要将元素保存在 std::vectors 中。除了将std::array 更改为std::vector 之外,Jarod42 的建议仍然有效。

标签: c++ types c++17 variadic-templates pack-expansion


【解决方案1】:

在阅读了 cmets 和提供的答案之后,由于语言本身的性质,我得出的结论是,由于类型的数量是固定的并且已知的,但它们的数量并不是模板甚至不会是必需的......它可以简单地归结为一个带有简单构造函数和移动语义的基本类,同时使用initialize_list。

class In{...};
class Out{...};

class MyObject {
private:
     std::vector<In> inputs_;
     std::vector<Out> outputs_;

public:
     MyObject(initializer_list<In> inputs, initializer_list<Out> outputs ) :
       inputs_( std::move(inputs) ),
       outputs_( std::move(outputs) )
     {}
};

编辑 - 我并不是想展示可以编译的代码,只是为了说明一个观点,不过,我已对其进行了修复,以便为未来的读者正确匹配。

【讨论】:

  • 您修复了我之前提到的问题,但遗憾的是 std::initializer_list 不适用于仅移动类型。因此,无论您打算如何尝试实现这一点,它都不适用于没有模板的std::unique_ptr。我建议您提出一个新问题,具体询问是否需要。我知道另一种实现方式,但我认为这将是题外话。
【解决方案2】:

根据您的 cmets,我猜您正在寻找类似的东西

template<typename T1, typename T2>
class MyObject {
    std::vector<T1> inputs_;
    std::vector<T2> outputs_;
public:
    MyObject(std::initializer_list<T1> inputs, std::initializer_list<T2> outputs)
      : inputs_(inputs), outputs_(outputs) { }
};

用作

MyObject obj({a, b, c}, {x, y});

请注意,这要求这两种类型都是可复制构造的。它不适用于仅移动类型。


如果您真的坚持将元素单独作为构造函数参数,这在技术上是可行的,但实现起来会更加复杂,而且没有什么好处。

【讨论】:

  • 这可以工作,但是,有没有办法在实例化对象时显式声明模板参数的类型?再说一次,只有两种不同的类型,那么它可能也不是什么大问题......
  • @FrancisCugler 是的,我会为此添加一个解决方案。
  • @FrancisCugler 用 C++17 说明,MyObject obj({a, b, c}, {x, y}); 应该工作感谢CTAD
  • OP 似乎甚至不需要模板,因为T1 固定为In,而T2 是Out。
  • @NathanOliver 我认为如果我让构造函数采用std::vector 就像我之前的示例一样,它将无法工作。我已将其修复为使用 std::initializer_list,它应该可以这样工作。
【解决方案3】:

我试图在施工时完成所有这些......

恕我直言,基于初始化列表的解决方案(请参阅 walnut 的答案)更可取,而且从调用者的角度来看,哪些参数是输入值,哪些是输出值。

但是...只是为了好玩...如果您真的想在构造时完成所有工作并避免对参数进行分组(或混合它们)...使用std::tuple、std::tuple_cat()、委托构造函数、std::apply()和if constexpr

#include <iostream>
#include <type_traits>
#include <tuple>
#include <vector>

struct TypeIn  {};
struct TypeOut {};

struct MyObject
 {
   template <typename TargetType, typename T>
   static auto filterVal (T && t)
    {
      if constexpr ( true == std::is_same_v<TargetType, std::decay_t<T>> )
         return std::tuple<TargetType>{std::forward<T>(t)};
      else
         return std::tuple<>{};
    }

   std::vector<TypeIn> is;
   std::vector<TypeOut> os;

   template <typename ... It, typename ... Ot>
   MyObject (std::tuple<It...> && ti, std::tuple<Ot...> && to)
    : is{ std::apply([](auto && ... ts){
                        return std::vector<TypeIn>{
                           std::forward<decltype(ts)>(ts)... }; }, ti) },
      os{ std::apply([](auto && ... ts){
                        return std::vector<TypeOut>{
                           std::forward<decltype(ts)>(ts)... }; }, to) }
    { }

   template <typename ... Ts>
   MyObject (Ts && ... ts)
    : MyObject{std::tuple_cat( filterVal<TypeIn>(std::forward<Ts>(ts)) ... ),
               std::tuple_cat( filterVal<TypeOut>(std::forward<Ts>(ts)) ... )}
    { }
 };

int main ()
 {
   TypeIn   a, b, c, d;
   TypeOut  e, f;

   MyObject mo{ a, b, c, d, e, f };

   std::cout << mo.is.size() << " TypeIn vals\n"
             << mo.os.size() << " TypeOut vals\n";
 }

我再说一遍:只是为了好玩。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-18
    • 2021-02-08
    • 2021-05-03
    • 2023-03-22
    • 1970-01-01
    • 2011-09-08
    • 2011-11-03
    • 2020-10-29
    相关资源
    最近更新 更多