为什么我只能从可变参数模板中提取一个值?

时间:2019-04-29 22:02:57

标签: c++

我正在编写一个程序,该程序将充当类之间的控制器。本质上,所有类都具有getter和setter,但是,这些getter和setter具有不同的名称,并且有些执行不同的操作。例如,ClassA有一个设置器setValue,但是ClassB有一个名为updateValue的设置器。这些类已经编写,并且我不想重写每个类以使用相同的名称,因为它们已经嵌入在代码库中。相反,我在每个类中编写了一个updateVariable函数,将其称为各自的设置器。

现在我已经解释清楚了,我将显示控制器类:

#ifndef CONTROLLER_HPP
#define CONTROLLER_HPP

#include <functional>
#include <vector>
#include <iostream>

class Controller
{

  public:
    template <class...Classes>
    Controller(Classes & ...classes) 
    { 
      toSet = [&](int val){(classes.updateValue(val), ...); }; 
      toGet = [&](){ return (classes.get(), ...); }; 
    }

    void setValues(int val)
    {
      toSet(val);
    }

    std::vector<int> getValues()
    {
      std::vector<int> values;
      values.emplace_back(toGet());
      return values;
    }

  private:
    std::function<void(int)> toSet;
    std::function<int()> toGet;
};

#endif

我仍然不确定toSet的工作方式,例如,如果我在main中声明了这一点:

int main()
{
  ClassA a;
  ClassB b;
  ClassC c;
  Controller control(A,B,C);
  control.setValues(20);
}

这会将所有类的值设置为20。但是我不知道魔术是如何工作的,因为它似乎只被调用了一次。但是,当我使用toGet尝试相同的操作时,我只能得到一个值。因此,我对为什么这不起作用感到困惑。

2 个答案:

答案 0 :(得分:4)

让我们从问题“ toSet的工作”这一部分开始。让我们摆脱variadic模板,使事情变得更干净:

Controller(ClassA& classA, ClassB& classB, Class C& classC)
{ 
  toSet = [&](int val){
    classA.updateValue(val);
    classB.updateValue(val);
    classC.updateValue(val);
  };
}

(我认为从技术上讲,它使用的是逗号运算符,但是足够接近了。)

本质上,这是在创建一个lambda,它为传入的三个实例分别调用updateValue。它会通过lambda捕获保存对这些实例的引用。

如果类似地扩展toGet代码,您会得到一些毫无意义的东西:

toGet = [&](){
    return classA.get(), classB.get(), classC.get();
};

换句话说,toGet仅能返回一个值,尽管它将调用所有三个getter。讨厌的逗号运算符!

相反,您可以更改toGet,以便它返回一个向量。基本上,您希望它这样做:

toGet = [&](){
    std::vector<int> values;
    values.emplace_back(classA.get());
    values.emplace_back(classA.get());
    values.emplace_back(classA.get());
    return values;
};

C ++ 17方法:如果您使用的是c ++ 17,并且可以摆脱使用模板类的麻烦,而不仅仅是制作set和get函数模板:

#include <functional>
#include <vector>
#include <iostream>
#include <utility>

template <typename... Class>
class Controller
{
  public:
    Controller(Class& ...objects) 
        : objects(objects...)
    { 
    }

    void getValuesInTo(std::vector<int> &values)
    {
        std::apply([&](auto... x){ (values.push_back(x.get()), ...);}, objects);
    }

  private:
    std::tuple<Class&...> objects;
};


struct ClassA
{
    int  get()                  { return 1;};
};

struct ClassB
{
    int  get()                  { return 2;};
};

struct ClassC
{
    int  get()                  { return 3;};
};


int main()
{
    ClassA a;
    ClassB b;
    ClassC c;
    auto control = Controller(a,b,c);
    std::vector<int> res;
    control.getValuesInTo(res);
    for(auto a  : res) std::cout << a;
}

答案 1 :(得分:1)

说明

在C ++中,在语句之间加上逗号会丢弃除最后一条语句以外的所有内容的返回值:

int x = (10, 20, "Hello", 30); 
// x is 30

逗号运算符在两个地方使用。在这里,使用toSet

toSet = [&](int val){(classes.updateValue(val), ...); }; 

在这里,用toGet

toGet = [&](){ return (classes.get(), ...); }; 

toSet可以正常工作,因为您不必担心updateValue的返回值。您要做的就是更新值。但是,toGet仅返回传入的最后一个值。

修复

如果get()对于所有类总是返回相同的类型(例如int),我们可以修改toGet来返回向量:

// Declaration: 
// std::function<std::vector<int>()> toGet; 
toGet = [&]() { return std::vector<int>{classes.get()...}; };