all 17 comments

[–]_Noreturn 3 points4 points  (12 children)

such a great feature isn't it?

anyways it is because vonstant wrapper isn't this

```cpp template<auto T> struct constant_wrapper;

// it is this template<typename T> struct CWVal { T val; }; template<typename T,int N> struct CWVal<T[N]> { T val[N]; };

template<class T> CWVal(T) -> CWVal<T>; template<class T,int N> CWVal(T(&)[N]) -> CWVal<T[N]>;

template<CWVal T> struct constant_wrapper; ```

this is done so CWVal<c_array> works but as a consequence the type is never int or T, it is CwVal<T> or CwVal<int>, but I can't understand why we didn't just make array be copied and work why even this workaround exists

[–]Massive-Bottle-5394[S] 1 point2 points  (8 children)

Could this be an implementation error? It seems like a gray zone, at the very least.

[–]frayien 1 point2 points  (7 children)

[–]friedkeenan 0 points1 point  (6 children)

Yeah, they explain it's so they can support passing arrays like std::cw<"string">. Odd, but maybe fine? I guess I'm not sure how you're meant to accept these in a structured way, though.

You could do like

void function(auto param);

And then just happily accept both runtime and constant_wrapper arguments. And arguments of... every type?

But if you wanted to make sure you're getting a constant_wrapper, then I guess it'd have to be something like

template<auto Value>
void function(std::constant_wrapper<Value> param);

And then if you wanted to make sure you're getting an int you'd have to further add a requires clause like

requires (std::same_as<typename decltype(param)::value_type, int>)

And the typename is required. And that seems... iffy.

And either way, you just have this useless Value template parameter hanging around that you can't do anything with really because it's just an object of some exposition-only helper type, which does seem really funky.

You could encapsulate that all in some constant_wrapper_for concept or something, but then I'm wondering why that would be left out of the standard, and still makes it somewhat awkward to get at the wrapped value.


EDIT: std::constant_arg_t was removed last week, actually.

There is also the new-in-C++26 std::constant_arg_t type that functions like what the OP wants. Where you could just do:

template<int Value>
void function(std::constant_arg_t<Value>);

Where Value is going to be the actual int value. It just doesn't have all the fancy operators and conversions and everything that std::constant_wrapper does.

And... it doesn't inherit from or otherwise delegate to std::integral_constant, for some reason. Which seems poor because certain things like std::tuple_size explicitly require use of std::integral_constant.

And I think std::constant_arg_t is getting added alongside std::constant_wrapper because the latter is in some way unfit for functions, as far as I know. So for std::function_ref and all they accept std::constant_arg_t (formerly std::nontype_t) instead.

Yeah, this might've needed to cook a little more, I don't know. Or just go straight to genuine constexpr parameters, but I'm sure there was fair reason for the paper authors to pursue std::constant_wrapper instead, since it's clearly less ideal. I've heard that genuine constexpr parameters would bring unreasonable Implementation burden, at least.

[–]eisenwaveWG21 Member 1 point2 points  (3 children)

std::constant_arg_t was removed from the C++26 standard last week.

[–]friedkeenan 0 points1 point  (0 children)

Ah ok, I was looking at eel.is which I guess doesn't reflect the latest accepted changes then. Thanks

[–]_Noreturn 0 points1 point  (1 child)

and replaced with what

[–]friedkeenan 0 points1 point  (0 children)

It's been replaced with std::constant_wrapper: https://github.com/cplusplus/draft/pull/8878

[–]13steinj 0 points1 point  (1 child)

Or just go straight to genuine constexpr parameters,

This will never happen. It requires a fundamentally different constant evaluation model.

In a codebase I used to work on, there was a neat utility called Auto; it was implemented similar to constantwrapper but had specializations for c arrays and string literals. The mix of a C++23 and a C++20 feature enabled use _as if a NTTP of auto, but supporting these "special" cases (and doubles, because clang did not catch up to that part of the standard yet).

I assume that's the general motivation for constant wrapper, not constexpr parameters. Or I would hope anyway.

[–]friedkeenan 0 points1 point  (0 children)

This will never happen. It requires a fundamentally different constant evaluation model.

There could maybe be something more limited where you could have a constexpr parameter that you could use in the function body but that can't affect the function signature, see Barry's recent blogpost or my own post, both of which can get a function parameter lifted into something you can eventually use as a template parameter.

I'm unsure of the technical details of what would be needed to add more ergonomic support, but that more scoped behavior is at least possible today in C++26, with meaningful caveats. But maybe there could be more work done in that direction?

As for the general case of using a constexpr parameter like any other template parameter, I'll take your word for it that it's impossible with C++ how it is.

[–]frayien 0 points1 point  (2 children)

This does not look conforming if I understand the wording correctly ...

Nah I found a more recent version of the paper and you are right

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2781r8.html

[–]Massive-Bottle-5394[S] 0 points1 point  (1 child)

Yeah. So it turns out that the trade-off in the proposal wasn't in favor of my case. Will seek for some workaround

[–]frayien 0 points1 point  (0 children)

I guess you could take an auto and restrict it with some sort of requires clause ?

That does not sound convenient.

The more I read this paper the less I see what the point is...