【问题标题】:How to prevent compilation of passed lambda, if arguments are not references如果参数不是引用,如何防止编译传递的 lambda
【发布时间】:2019-06-07 10:31:33
【问题描述】:

在我的一个项目中,我使用了一个小型实用函数,它采用 Message 结构和 lambda 函数来修改此消息结构。

现在,我无意中传递了一个没有必要引用 & 的 lambda。它可以完美编译,但没有提供所需的输出。

就我而言,应该是以下两种行为之一:

  1. 忘记写auto&,而只写auto 会导致编译错误
  2. 只写auto 应解释为auto&。

如果缺少&,是否可以阻止编译,或者更好地将auto自动解释为auto&?

#include <iostream>
#include <functional>
#include <boost/variant.hpp>

struct Message {
    int x;
    int y;
};

void changeMessage(Message& m, const std::function<void(Message&)>& messageModifier) {
    std::cout << "Message before:" << m.x << " " << m.y << "\n";
    messageModifier(m);
    std::cout << "Message after:" << m.x << " " << m.y << "\n";
}

int main(int, char**) {
    {
        std::function<void(int&)> f = [](int&) {};
        std::function<void(int)> g = [](int) {};
        f = g; // This compiles. 
    }

    {
        std::function<void(int&)> f = [](int&) {};
        std::function<void(int)> g = [](int) {};
        //g = f; // This does not compile. Makes perfect sense.
    }

    Message m{ 10,20 };
    {
        changeMessage(m, [](auto m) { m.x++; m.y--; }); // User unintentionally forgot &! Can I prevent this from compilation?
        std::cout << "Message outside: " << m.x << " " << m.y << "\n";
    }
    {
        changeMessage(m, [](auto& m) { m.x++; m.y--; });
        std::cout << "Message outside: " << m.x << " " << m.y << "\n";
    }
}

【问题讨论】:

  • 简短回答:不,C++ 不能以这种方式工作。防止可调用对象的参数成为非引用类型的唯一方法是,在这种情况下,可调用对象本身的某些内容无法编译。当然,有一些奇特的解决方案涉及使用基于模板的脚本生成代码或声明,这将确保参数具有正确的类型。
  • “或者甚至更好地将auto 自动解释为auto&amp;?” - 如果可能的话,恐怕会发生多少破损。不,这无论如何都不是更好。您要求替换一个您可以通过打开可能需要数小时或数天才能追踪的错误轻松发现的错误。处理悬空引用比较困难。
  • 使Message 成为不可复制的对象:Message(const Message&amp;) = delete;。
  • 从技术上讲,您可以使用 !is_invocable_v&lt;..., Message&amp;&amp;&gt; 之类的东西来确保 lambda 不接受右值。
  • 您可以要求 lambda 返回修改后的消息。那将使您担心的那种错误变得不可能。这是使用值语义的优势之一。

标签: c++ lambda closures std-function


【解决方案1】:

防止通过值传递Message(而auto 本身绝不是引用)的一种方法是禁用复制构造:

struct Message {
    Message() = default;
    Message(const Message&) = delete;

    int x;
    int y;
};

@L. F. 建议的另一个解决方案是检查 lambda 是否不接受右值:

template<class Fn>
void change_message(Message& m, Fn fn) {
    static_assert(!std::is_invocable_v<Fn, Message&&>);     
    fn(m);
}

【讨论】:

  • 如果你想让Message 可复制,但不是隐式的,你可以在复制构造函数上使用标签调度和重载。
  • 基本思路可以看here。不过,可能应该做到[[maybe_unused]] const implicit_copy_tag。
  • @Fureeish,有趣的想法,谢谢!奇怪的是,如果你有void foo(Message),不使用foo({...})语法怎么调用这个函数呢?
  • @Fureeish,你没有命名implicit_copy_tag参数,所以[[maybe_unused]]似乎是多余的,还有const。
  • @Fureeish,未使用的命名变量。
猜你喜欢
  • 2011-02-25
  • 2012-07-29
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 2019-11-25
  • 1970-01-01
  • 2015-10-29
  • 2014-06-08
相关资源
最近更新 更多