【问题标题】:Conversion from lambda to non-scalar type requested请求从 lambda 转换为非标量类型
【发布时间】:2020-12-07 17:06:40
【问题描述】:

我创建了这个类,因此我可以拥有任何类型的值,该值可以是固定的,也可以在每次使用时重新计算:

template<typename T>
class Value {
    private:
    bool fixed;
    union {
        T value;
        std::function<T()> get;
    };
    public:
    Value(const T& value) : fixed(true), value(value) {}
    Value(const std::function<T()>& get) : fixed(false), get(get) {}
    Value(const T *pointer) : Value([pointer]() { return *pointer; }) {}
    ~Value() {}
    operator T() { return fixed ? value : get(); }
};

以下所有表达式似乎都可以正常工作:

Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });

但是当我尝试这样做时:

Value<double> f = []() { return 1.; };

触发编译错误:

error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested

你可以试试这个例子here

为什么分配适用于T 而不是std::function&lt;T()&gt;,我怎样才能做到这一点?

注意:我知道this answer,但我不清楚如何解决这个问题,而不必像为Value&lt;double&gt; e 那样显式调用构造函数。

【问题讨论】:

  • @SilvioMayolo 刚刚添加了一个指向我正在测试的平台的链接。
  • 在你的godbolt链接中,这行写着Value&lt;int&gt; e = Value&lt;int&gt;([]() { return 1; });,而这里的Value&lt;double&gt; e = Value&lt;int&gt;([]() { return 1.; });是一个错误
  • @largest_prime_is_463035818 应该修复。我将所有内容都转换为双精度以简化问题,但忘记在 Godbolt 上进行更改。
  • 你可以写auto f = Value&lt;int&gt;([]() { return 1; });如果你担心把类型拼写两次。它基本相同,并且有效;)
  • 我建议用std::variant 替换这个类的内部结构。此外,对于前两个构造函数,由于 Value 想要“获得”(即保存)参数,您应该按值(无引用限定符)和 std::move 将参数设置为数据成员。这样,调用者可以控制是复制还是移动,而不是强制复制。

标签: c++ templates lambda std-function


【解决方案1】:

为什么分配对 T 而不是 std::function 有效,我怎样才能做到这一点?

你的代码没有使用赋值,而是copy initialization

此外,复制初始化中的隐式转换必须直接从初始化程序生成 T,而例如直接初始化需要从初始化程序隐式转换为 T 的构造函数的参数。

所以要让它工作,你必须让你的ctor直接接受lambda(这是一个简化的例子):

template<typename T>
class Value {
    std::function<T()> get;    
public:
    
    template<class Y>
    Value(Y lambda ) : get( std::move( lambda ) )  {}
};

live code 你可能想使用std::enable_if 或概念来添加限制,如果 C++20 被允许到这个 ctor 以及在这种形式下这个构造函数会尝试接受所有其他重载不会并且可能产生神秘的错误.根据这个enable_if template param is lambda (with particular signature),它可能很简单

template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) )  {}

支持 C++14。这是yet another live example,你可以看到这个构造函数没有用于int类型的初始化器:

 Value<double> d2 = 123;

prog.cpp:9:5:注意:候选模板被忽略:替换失败 [with Y = int]:调用的对象类型“int”不是函数或函数指针 值(Y λ):get(std::move(λ)){}

【讨论】:

【解决方案2】:

lambda 不是std::function。这意味着当你这样做时

Value<double> f = []() { return 1.; };

您需要将[]() { return 1.; } 转换为std::function,这是用户定义的转换,然后您需要将该std::function 转换为Value&lt;double&gt;,这是另一个用户定义的转换。当您只被允许进行一次此类转换时,这是两次用户定义的转换。这就是代码无法编译的原因。

【讨论】:

  • 您可以通过使用其他构造函数语法Value&lt;double&gt; f { []() { return 1.; } }; 绕过它,它被视为显式构造函数调用,因此不是额外的转换。
  • 我可以通过修改类使其工作吗?
  • @SilvioMayolo 是的,你有一个用户定义的从 lambda 类型到 std::function 的转换,然后 std::function 对象用于直接初始化。
  • @cabralpinto 你可以。您需要向 Value 添加一个模板构造函数,该构造函数采用任何类型的可调用对象。这样您就可以按原样使用 lambda,然后就可以了。
  • 您是否可以编辑答案以显示这些更改?我对 C++ 很陌生,仍在尝试找出模板。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-22
  • 1970-01-01
  • 2022-12-17
  • 1970-01-01
  • 2015-12-01
  • 1970-01-01
相关资源
最近更新 更多