【问题标题】:How to std::bind() to create a data member?如何 std::bind() 创建数据成员?
【发布时间】:2014-01-26 22:05:19
【问题描述】:

我正在使用 C++11 不错的新生成器和分布生成随机值。在一个函数中,它就像一个魅力,看起来像这样:

void foo() {
   mt19937 generator;
   uniform_int_distribution<unsigned> distribution;
   auto dice = bind(distribution, generator);
   // dice() will now give a random unsigned value
}

但是我怎样才能将所有三个对象都放在一个类中作为数据成员呢?我可以简单地将generatordistribution 写为数据成员,但是如何在不知道(或想知道)其确切类型的情况下使dice 成为数据成员?没想到这个

class X {
   mt19937 generator;
   uniform_int_distribution<unsigned> distribution;
   decltype(bind(distribution, generator)) dice;
};

在 Visual Studio 2013 中产生错误 error C2660: 'bind' : function does not take 2 arguments

【问题讨论】:

  • @juanchopanza ...这是以一些开销为代价的。
  • 看起来像一个 MSVC 错误。
  • 仅供参考,bind 将创建其参数的副本,因此如果您要创建 dice2,它将使用与 dice 不同的分布和 RNG。为避免这种情况,您需要将参数包装在 reference_wrapper
  • 标准对从std::bind 返回的类型的默认和/或复制构造有何看法?编辑:20.8.9.1.2/5“备注:返回类型应满足MoveConstructible的要求。”
  • @chris 好的,找到了。它在 C++11 中是合法的。非静态数据成员可能出现在未计算的操作数中。

标签: c++ c++11 member stdbind


【解决方案1】:

你总是可以gasp编写一个函数而不是使用 lambda/bind/etc.:

class X {
   mt19937 generator;
   uniform_int_distribution<unsigned> distribution;
public:
   auto dice() -> decltype(distribution(generator)) {
     return distribution(generator);
   }
   // or alternatively
   auto operator() () -> decltype(distribution(generator)) {
     return distribution(generator);
   }
};

参数化生成器和/或分布的类型以及使用std::shared_ptr 保存生成器的奖励积分,以便您可以创建具有不同分布的多个对象,这些对象共享相同的引擎。您最终还需要一个构造函数来为生成器播种 - 理想情况下使用 std::random_device{}() 之类的东西。

或者,the answer I think you are looking for

class X {
   mt19937 generator{std::random_device{}()};
   uniform_int_distribution<unsigned> distribution{1,6};
public:
   decltype(bind(std::ref(distribution), std::ref(generator))) dice{
     bind(std::ref(distribution), std::ref(generator))
   };
};

很抱歉,我首先嘲笑了您尝试使用bind:您可以在 C++11 中使用“无代码”编写此类,这实际上是一种简洁。我们需要对 C++17 中的类成员声明进行类型推断,所以这可能是:

class X {
   auto generator = mt19937{std::random_device{}()};
   auto distribution = uniform_int_distribution<unsigned>{1,6};
public:
   auto dice = bind(std::ref(distribution), std::ref(generator));
};

鉴于the latest Concepts Lite paper 建议在语言中的任何位置使用概念名称,其中auto 可能表示“推断类型,如果类型不模拟命名概念则格式错误”,auto 成员声明可能不会出现问题。

【讨论】:

  • 但是std::bind 的重点是……喘气……让这一切变得不必要。 (并不是说我认为这段代码本身就是一个糟糕的解决方案……)
  • @KonradRudolph:我真的不认为这是绑定的重点。据我所知,绑定的目的是让人们不必编写一次性函数或函子,与它们的使用点分开。在这种情况下,它并没有真正发挥这个作用。
  • 我不确定我知道bind 的意义是什么,因为我们在语言中有 lambda。 C++14 中的通用 lambda 只会让可怜的 bind 变得更糟。我承认,对于这种特定情况,bind(distribution, generator)[this]{return distribution(generator);} 更简洁。
  • 它可能会为您节省一些字符,但我仍然更喜欢 lambda。在我看来,Lambda 比 bind 更容易理解,因为它们采用类似于普通函数的形式,而且我们不断地编写函数。
  • @BenjaminLindley 完全同意:bind 实际上是一个我必须考虑的迷你 DSL,而不是像任何其他 C++ 代码那样简单地阅读。 Lambda 最终也因简洁而胜出:bind(std::ref(distribution), std::ref(generator))[this]{return distribution(generator);}
【解决方案2】:

It works on GCC。我很确定这只是一个编译器错误。不幸的是,这意味着您必须吃苦头并使用其他答案中描述的解决方法之一。

【讨论】:

    【解决方案3】:

    std::bind 的结果未指定:这意味着您无法在没有类型推断的情况下存储其原始结果。但是,您可以使用std::function 来封装bind 的结果:

    #include <functional>
    std::function<unsigned()> dice(std::bind(distribution, generator));
    auto result = dice();
    

    编辑:正如上面所说的,这显然是 Visual Studio 的问题。我可以确认这与 VS2013 一起编译:

    #include <functional>
    #include <random>
    
    using namespace std;
    
    class X {
        mt19937 generator;
        uniform_int_distribution<unsigned> distribution;
        std::function<unsigned()> dice;
    
    public:
        X() : dice(bind(distribution, generator)) {}
    
        unsigned roll() { return dice(); }
    };
    

    但是将 dice 的类型更改为 decltype(bind(distribution, generator)) 会使整个事情失败并表现出色(即使它仍然适用于 clang)。

    【讨论】:

    • 但是decltype 使用类型推断,不是吗?
    • 抱歉,是的。那么让我进一步测试一下。但是请注意,在 VS2013 之前,Microsoft 对可变参数模板的实现已经被破坏和破解,因此 std::function 几乎可以肯定会解决这个问题。
    • 在 VC++ 2013 之前,Microsoft 的可变参数模板实现不存在。;-]
    • 我知道,但他们有丑陋的 hack 来支持需要可变参数的几种方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 2016-10-04
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多