3

Background

I am using a NTTP (non-type template parameter) lambda to store a string_view into a type at compile time:

template<auto getStrLambda>
struct MyType {
    static constexpr std::string_view myString{getStrLambda()};
};

int main() {
    using TypeWithString = MyType<[]{return "Hello world";}>;
    return 0;
}

This works, and achieves my main intention.

Question

My question now is, to make this easier to use, how can I write a wrapper function to create the lambda for me?

I'm thinking something like:

// This helper function generates the lambda, rather than writing the lambda inline
consteval auto str(auto&& s) {
    return [s]() consteval {
        return s;
    };
};

template<auto getStrLambda>
struct MyType {
    static constexpr std::string_view myString{getStrLambda()};
};

int main() {
    using TypeWithString = MyType<str("Hello world")>;
    return 0;
}

The above fails on clang since the lambda isn't structural, since it needs to capture the string:

error: type '(lambda at <source>:4:12)' of non-type template parameter is not a structural type
    using TypeWithString = MyType<str("Hello world")>;
                                  ^
note: '(lambda at <source>:4:12)' is not a structural type because it has a non-static data member that is not public
    return [s]() consteval {
            ^

Given that it's possible for a lambda to use a variable without capturing it if the variable is initialized as a constant expression (source), how can I define a function to parameterize this lambda return value at compile time?

10
  • 1
    Does this answer your question? Passing a string literal to a template char array parameter Commented Oct 14, 2022 at 17:53
  • "I am using a NTTP lambda to store a string_view into a type at compile time" Why does that "string_view" have to be a view of a string literal? That's the core of the problem here: string literals, or any pointer into such a string, can never be an NTTP. Ever. Not as a member of a structural type. Nowhere. You are not allowed to smuggle string literals into template substitution. So don't try; just store an array of characters. Commented Oct 14, 2022 at 17:55
  • @NicolBolas I'm not trying to pass a string literal, or pointer to string literal as the NTTP, the NTTP is a lambda that returns a string literal. Or are you saying that would also be UB? Commented Oct 14, 2022 at 18:07
  • @HolyBlackCat that doesn't actually answer my specific question, but it does provide another way to create a class template that can store a string at compile-time without the need for a consteval lambda. I may just use that, thanks! Commented Oct 14, 2022 at 18:24
  • 2
    @JamesMart: "the NTTP is a lambda that returns a string literal." You want the lambda to store the literal. That means the literal must be a member of the lambda via capture. Commented Oct 14, 2022 at 18:26

1 Answer 1

5

This is CWG2542.

Prior to the linked issue's resolution, GCC and Clang are both not actually incorrect in accepting or rejecting the program. The lambda's type simply has a data member of type char[12], which was allowed to be public or private. It seems that Clang treats them as private members.

But now, after CWG2542, capturing lambdas cannot have structural types, so you have to do something else.

The obvious solution is to write out the closure type explicitly ensuring the data member is always public:

consteval auto str(auto&& s) {
    static_assert(std::is_array_v<std::remove_reference_t<decltype(s)>>);
    return [&]<std::size_t... I>(std::index_sequence<I...>) {
        struct {
            std::remove_cvref_t<decltype(s)> value;
            consteval std::decay_t<decltype(s)> operator()() const {
                return value;
            }
        } functor{{std::forward<decltype(s)>(s)[I]...}};
        return functor;
    }(std::make_index_sequence<std::extent_v<std::remove_reference_t<decltype(s)>>>{});
}

The great thing about this is that it will have the same type for strings of the same length, so MyType<str("xyz")> in one translation unit will mangle to the same name as MyType<str("xyz")> in another, since it stores an array.

Your goal of str("string literal") being a function call and return something "without any capture" is impossible, since the function argument auto&& s is not usable in a constant expression. In particular, you can't convert it to a pointer nor can you access any of its items.

You can also have str be a type and skip the step of a function:

template<typename T>
struct str;

template<typename T, std::size_t N>
struct str<T[N]> {
    T value[N];

    constexpr str(const str&) = default;
    consteval str(const T(& v)[N]) : str(std::make_index_sequence<N>{}, v) {}

    consteval auto operator()() const {
        return value;
    }
private:
    template<std::size_t... I>
    consteval str(std::index_sequence<I...>, const T(& v)[N]) : value{ v[I]... } {}
};

template<typename T, std::size_t N>
str(const T(&)[N]) -> str<T[N]>;

Where str("string literal") is now a structural type holding an array.

Sign up to request clarification or add additional context in comments.

7 Comments

Won't the reference-to-array decay into a pointer-to-first-element when captured by the lambda? (not in your code; in the OP's code)
@Yakk-AdamNevraumont From eel.is/c++draft/expr.prim.lambda#capture-15: "For array members, the array elements are direct-initialized in increasing subscript order." (lambdas have array members if they capture arrays). If they had [s=s] then it would decay
> The lambda's type simply has a data member of type char[12] Why? Why wouldn't the lambda type define inline const char * operator()() const { return "Hello world"; }?
The index_sequence part can be removed, the chars can be copied in a simple loop.
@JamesMart The lambda inside your str(auto&& s) function which captures s does. The []{return "Hello world";} lambda doesn't, but as I've explained, once you've passed s as a function argument it is no longer a constant expression so must be captured.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.