【问题标题】:Not a constant expression in lambda function不是 lambda 函数中的常量表达式
【发布时间】:2020-06-08 14:45:45
【问题描述】:

我有以下代码:

#include <boost/hana.hpp>
#include <array>
#include <iostream>
#include <utility>

namespace hana = boost::hana;

#define HEADER_CONNECT 0b00010000
#define HEADER_CONNACK 0b00001000

struct ConnectFrame
{
    uint8_t header = 16;
    uint8_t variable = 2;
};

struct ConnackFrame
{
    uint8_t header = 8;
    uint8_t variable = 3;
};

constexpr auto FramesMap = hana::make_tuple(
    hana::make_pair(hana::type_c<ConnectFrame>, hana::integral_c<std::uint8_t, HEADER_CONNECT>),
    hana::make_pair(hana::type_c<ConnackFrame>, hana::integral_c<std::uint8_t, HEADER_CONNACK>));

//! Runtime deserialization switch based on FramesMap
template <typename InputIterator>
auto deserializeByFrameHeader(const std::uint8_t frameHeader, const InputIterator buffer)
{
    auto found = hana::index_if(FramesMap, [&frameHeader = std::as_const(frameHeader)](auto const &pair) {
        return hana::second(pair) == hana::integral_c<std::uint8_t, frameHeader>;
    });
    auto FrameType = hana::first(hana::at(FramesMap, found.value()));
    using T = typename decltype(FrameType)::type;
    T var;
    //deserialize(buffer, var);
    return var;
}

int main()
{
    std::array<std::byte, 128> buffer;
    // for dummy purposes we assume that the first byte of the buffer array after serialization is 8
    const uint8_t header = 8;
    ConnackFrame frameOut = deserializeByFrameHeader(header, buffer.begin());
}

Live demo

我尝试在元组中找到与变量 frameHeader 匹配的对的索引。不幸的是,我得到一个编译错误:

../include/minimalMQTT.hpp:178:43: error: 'this' is not a constant expression
  178 |                 return hana::second(pair) == hana::integral_c<std::uint8_t, frameHeader>;
      |                        ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

如何将变量frameHeader 声明为常量表达式以使其工作?

【问题讨论】:

  • 我试图提供一个最小的工作示例,但是它需要 boost/hana 可用...
  • 函数参数永远不是constexpr,所以integral_c不可能这样工作。您需要将其作为模板参数传递给deserializeByFrameHeader

标签: c++ boost c++17 boost-hana


【解决方案1】:
hana::integral_c<std::uint8_t, frameHeader>

integral_c 是一种编码静态已知值的类型。但是,您尝试使用静态未知的 frameHeader 实例化它。

要将运行时值映射到 compile-tume 值上,最好的方法是映射(有时使用二进制搜索)。但是,您也可以检查是否需要评估此编译时间。

解决方法

在您的特定示例中,您可以通过使用 constexpr lambda 来解决问题(考虑到足够新的编译器!)。

在实践中,我怀疑这是否适合您的需求,但只是为了让您了解诀窍:

Live On Wandbox

#include <boost/hana.hpp>
#include <boost/core/ignore_unused.hpp>
#include <array>
#include <iostream>
#include <utility>

namespace hana = boost::hana;

#define HEADER_CONNECT 0b00010000
#define HEADER_CONNACK 0b00001000

struct ConnectFrame
{
    uint8_t header = 16;
    uint8_t variable = 2;
};

struct ConnackFrame
{
    uint8_t header = 8;
    uint8_t variable = 3;
};

constexpr auto FramesMap = hana::make_tuple(
    hana::make_pair(hana::type_c<ConnectFrame>, hana::integral_c<std::uint8_t, HEADER_CONNECT>),
    hana::make_pair(hana::type_c<ConnackFrame>, hana::integral_c<std::uint8_t, HEADER_CONNACK>));

//! Runtime deserialization switch based on FramesMap
template <typename FrameHeader, typename InputIterator>
auto deserializeByFrameHeader(FrameHeader const frameHeader, const InputIterator buffer)
{
    auto found = hana::index_if(FramesMap, [=](auto const &pair) constexpr {
        return hana::second(pair) == hana::integral_c<std::uint8_t, frameHeader()>;
    });
    auto FrameType = hana::first(hana::at(FramesMap, found.value()));
    using T = typename decltype(FrameType)::type;
    T var;
    boost::ignore_unused(buffer);
    //deserialize(buffer, var);
    return var;
}

int main()
{
    std::array<std::byte, 128> buffer;
    // for dummy purposes we assume that the first byte of the buffer array after serialization is 8
    ConnackFrame frameOut = deserializeByFrameHeader(
            []() constexpr { return 8; },
            buffer.begin());

    boost::ignore_unused(frameOut);
}

更新

从技术上讲,标头是缓冲区数组的第一个字节,即 uint8_t header = (uint8_t)buffer[0]。是否可以省略 header 参数并直接从缓冲区中提取 header 作为 constexpr?

没有。

返回类型是固定的。输入是动态的。没有办法(有用/有效地)铺平它。

顺便说一句,如果我不需要评估编译时间,有什么解决方案?

由于您正在解析协议消息,因此您自然会打开类型 ID(因为它们就是这样存在于网络上的)。作为一个认真的 C++ 程序员,你自然希望尽快将抽象层跳到proper type-switching

  • 老式技术是动态多态性(虚拟接口和继承)
  • 现代机制包括std::variant&lt;...&gt; 与访问。

根据您的使用模式和处理需求,可能更适用。 std::variant 有一个很好的特性,它以可切换的方式对类型进行编码,但访问保留了静态类型信息。这意味着:从技术上讲,您可以利用静态类型信息、内联和所有优化优点。

看来这就是你所追求的。所以我建议:

Live On Coliru

#include <array>
#include <iostream>
#include <variant>

constexpr uint8_t HEADER_CONNECT = 0b00010000;
constexpr uint8_t HEADER_CONNACK = 0b00001000;

struct ConnectFrame {
    uint8_t header = 16;
    uint8_t variable = 2;
};

struct ConnackFrame {
    uint8_t header = 8;
    uint8_t variable = 3;
};

// Static typed land
void handler(ConnectFrame const&) { std::cout << "Handling ConnectFrame\n"; }
void handler(ConnackFrame const&) { std::cout << "Handling ConnackFrame\n"; }

template <typename InputIterator>
void deserialize(InputIterator&, ConnectFrame&) { /*TODO*/ }

template <typename InputIterator>
void deserialize(InputIterator&, ConnackFrame&) { /*TODO*/ }

template <typename Frame, typename InputIterator>
Frame deserialize(InputIterator& buffer) {
    Frame frame;
    deserialize(buffer, frame);
    return frame;
}

// Type-swithcing land
template <typename InputIterator>
constexpr inline std::uint8_t frameHeader(InputIterator& buffer) {
    return static_cast<std::uint8_t>(*buffer++);
}

using AnyFrame = std::variant<ConnectFrame, ConnackFrame>;

template <typename InputIterator>
AnyFrame deserializeByFrameHeader(InputIterator&& buffer) {
    switch (uint8_t h = frameHeader(buffer)) {
        case HEADER_CONNECT: return deserialize<ConnectFrame>(buffer);
        case HEADER_CONNACK: return deserialize<ConnackFrame>(buffer);
    }
    throw std::range_error("frameHeader");
}

int main() {
    constexpr auto process = [](auto const& frame) { handler(frame); };
    using Buffer = std::array<std::byte, 128>;

    for (auto buffer : { Buffer 
        { std::byte(HEADER_CONNECT), std::byte(0x12), std::byte(0x34), },
        { std::byte(HEADER_CONNACK), std::byte(0xab), std::byte(0xcd), } })
    {
        auto frameOut = deserializeByFrameHeader(buffer.begin());
        std::visit(process, frameOut);
    }
}

打印出来的

Handling ConnectFrame
Handling ConnackFrame

使用 Hana 映射

如果您真的认为从映射表开始工作很重要,您可以使用更多代码和编译器:

constexpr auto FramesMap = hana::make_tuple(
    hana::make_pair(hana::type_c<ConnectFrame>, HEADER_CONNECT),
    hana::make_pair(hana::type_c<ConnackFrame>, HEADER_CONNACK)
);

请注意我是如何删除 integral_c 的,因为我们不需要它

AnyFrame 成为帧类型的变体:

constexpr auto FrameTypes = hana::transform(FramesMap, hana::first);

using AnyFrame = decltype(
        hana::unpack(FrameTypes, hana::template_<std::variant>))
    ::type;

现在,让我们使用它重新实现deserializeByFrameHeader

template <typename InputIterator>
AnyFrame deserializeByFrameHeader(InputIterator&& buffer) {
    AnyFrame retval;

    hana::for_each(FramesMap,
        [&, frameHeader = frameHeader(buffer)](auto const &pair) {
            auto first = hana::first(pair);
            using T = typename decltype(first)::type;

            if (hana::second(pair) == frameHeader) {
                retval.emplace<T>();
                deserialize(buffer, std::get<T>(retval));
            }
        });

    return retval;
}

注意简化:我们根据多态 lambda 中元组元素 (pair) 的静态类型保留所有内容,在此我们始终拥有可用的帧类型。

完整演示

Live On Coliru

#include <cstdint>
constexpr uint8_t HEADER_CONNECT = 0b00010000;
constexpr uint8_t HEADER_CONNACK = 0b00001000;

struct ConnectFrame {
    uint8_t header = 16;
    uint8_t variable = 2;
};

struct ConnackFrame {
    uint8_t header = 8;
    uint8_t variable = 3;
};

#include <boost/hana.hpp>
#include <stdexcept>
#include <variant>
#include <iostream>

namespace {
    namespace hana = boost::hana;

    constexpr auto FramesMap = hana::make_tuple(
        hana::make_pair(hana::type_c<ConnectFrame>, HEADER_CONNECT),
        hana::make_pair(hana::type_c<ConnackFrame>, HEADER_CONNACK)
    );

    constexpr auto FrameTypes = hana::transform(FramesMap, hana::first);

    using AnyFrame = decltype(
            hana::unpack(FrameTypes, hana::template_<std::variant>))
        ::type;
}

// Static typed land
void handler(ConnectFrame const&) { std::cout << "Handling ConnectFrame\n"; }
void handler(ConnackFrame const&) { std::cout << "Handling ConnackFrame\n"; }

template <typename InputIterator>
void deserialize(InputIterator&, ConnectFrame&) { /*TODO*/ }

template <typename InputIterator>
void deserialize(InputIterator&, ConnackFrame&) { /*TODO*/ }

// Type-swithcing land
template <typename InputIterator>
constexpr inline std::uint8_t frameHeader(InputIterator& buffer) {
    return static_cast<std::uint8_t>(*buffer++);
}

template <typename InputIterator>
AnyFrame deserializeByFrameHeader(InputIterator&& buffer) {
    AnyFrame retval;

    hana::for_each(FramesMap,
        [&, frameHeader = frameHeader(buffer)](auto const &pair) {
            auto first = hana::first(pair);
            using T = typename decltype(first)::type;

            if (hana::second(pair) == frameHeader) {
                retval.emplace<T>();
                deserialize(buffer, std::get<T>(retval));
            }
        });

    return retval;
}

#include <array>
int main() {
    constexpr auto process = [](auto const& frame) { handler(frame); };
    using Buffer = std::array<std::byte, 128>;

    for (auto buffer : { Buffer 
        { std::byte(HEADER_CONNECT), std::byte(0x12), std::byte(0x34), },
        { std::byte(HEADER_CONNACK), std::byte(0xab), std::byte(0xcd), } })
    {
        auto frameOut = deserializeByFrameHeader(buffer.begin());
        std::visit(process, frameOut);
    }
}

打印

Handling ConnectFrame
Handling ConnackFrame

【讨论】:

  • 从纯函数式教条中获取一页:如果你有 constexpr lambdas,一切都是函数工作wandbox.org/permlink/brQ1XoAq2BzvCTzO
  • 从技术上讲,标头是缓冲区数组的第一个字节,即uint8_t header = (uint8_t)buffer[0]。是否可以省略 header 参数并直接从缓冲区中提取 header 作为 constexpr?
  • 顺便说一句,如果我不需要评估编译时间,有什么解决方案?
  • @P.Zeugin 我学到了更多的 Hana 来为这些问题找到好的答案。请参阅 UPDATE 部分(一个没有 Hana 的实现,一个有)。干杯
猜你喜欢
  • 2017-03-30
  • 2021-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-20
  • 1970-01-01
  • 2015-09-16
  • 1970-01-01
相关资源
最近更新 更多