c++lambdac++17constexprstdtuple

Using lambda function in constexpr constructor with std::tie


I'm trying to use a constexpr constructor in C++17 with a lambda that uses std::tie to initialize fields in a class from a tuple. The code is similar to this:

#include <tuple>

enum class Format {
    UINT8,
    UINT16
};

struct FormatInfo {
    const char* name = nullptr;
    int maxVal = 0;

    constexpr explicit FormatInfo(Format fmt) {
        auto set = [this](const auto&... args) constexpr {
            std::tie(name, maxVal) = std::make_tuple(args...);
        };

        switch(fmt) {
        case Format::UINT8:  set("uint8", 255); break;
        case Format::UINT16: set("uint16", 65535); break;
        }
    }
};

int main() {
    FormatInfo info(Format::UINT8); // ok
    constexpr FormatInfo info2(Format::UINT8); // fails
}

Calling the constructor as constexpr fails, with an error that there is a call to a non-constexpr function inside set. Even though both std::tie and std::make_tuple should be constexpr.

Making the lambda itself constexpr (constexpr auto set = ...) also fails with an error that this is not a constant expression.

Is there any way to make this work in C++17?


Solution

  • std::tie returns a tuple of reference types. std::tuple<...>::operator= is not marked constexpr in C++17.

    You can emulate what operator= does with other (constexpr) functions:

    auto set = [this](const auto&... args) {
        std::apply([&](auto&... tied) {
            (void(tied = args), ...);
        }, std::tie(name, maxVal));
        // std::tie(name, maxVal) = std::make_tuple(args...);
    };
    

    Or you can ditch the tuples:

    auto set = [this](const auto& name, const auto& maxVal) {
        this->name = name;
        this->maxVal = maxVal;
    };