Passing Constexpr Objects Around

Passing constexpr objects around

The reason is that inside a constexpr function, parameters aren't constant expressions, regardless of whether the arguments are. You can call constexpr functions inside others, but the parameters of a constexpr function aren't constexpr inside, making any function call (even to constexpr functions) not a constant expression - inside.

const auto nOpen = numOpen( str );

Suffices. Only once you view the call from outside the constexpr-ness of the expressions inside is verified, deciding whether the whole call is constexpr or not.

Why passing constexpr object by const reference works, but by value doesn't compile

You are missing an initializer on iseq. You have to add it:

constexpr std::integer_sequence<int, 1,2,3,4> iseq{};
^^

From [dcl.constexpr]:

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have
literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.20). Otherwise, or if a constexpr specifier is used in a reference declaration, every fullexpression
that appears in its initializer shall be a constant expression. [ Note: Each implicit conversion
used in converting the initializer expressions and each constructor call used for the initialization is part of
such a full-expression. —end note ]
[ Example:

struct pixel {
int x, y;
};
constexpr pixel ur = { 1294, 1024 }; // OK
constexpr pixel origin; // error: initializer missing

—end example ]

Moreover, as Columbo suggests in his comment and answer, simply being initialized is insufficent. A user-provided constructor is also required, as per [dcl.init]:

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type
with a user-provided default constructor.

It's a little odd to have the most relevant section (dcl.constexpr) have an incomplete description of the requirements for a constepxr object declaration.

Is there any way for constexpr objects to refer/point to other non-static constexpr objects?

Yes, your example is conforming.

The special thing about C++14 relaxed constexpr is that intermediate results inside the evaluation of a constant expression do not themselves need to be constant expressions.

return applies an lvalue-to-rvalue conversion to b.a.x, because the function returns by value, and b.a.x is:

a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e

(N4527 §5.20/2.7.4)

If you tried to save a (dangling) reference to b.a.x, that would be a problem. That would not be a "permitted result of a constant expression" per your quote.

C++11 constexpr function pass parameter

A constexpr function and a constexpr variable are related, but different things.

A constexpr variable is a variable whose value is guaranteed to be available at compile time.

A constexpr function is a function that, if evaluated with constexpr arguments, and behaves "properly" during its execution, will be evaluated at compile time.

If you pass a non-constexpr int to a constexpr function, it will not magically make it evaluated at compile time. It will, however, be allowed to pass the constexprness of its input parameters through itself (normal functions cannot do this).

constexpr on functions is a mixture of documentation and restriction on how they are written and instructions to the compiler.

The reason behind this is to allow the same function to be evaluated both at compile time, and at run time. If passed runtime arguments, it is a runtime function. If passed constexpr arguments, it may be evaluated at compile time (and will be if used in certain contexts).

Note that consteval may be what you are looking for for a function. But maybe not.

You are getting errors because by passing in runtime values, you cannot get a compile time value out.

There are ways around this. My favorite is a std::variant of std::integer_constant; you can pick which is active at runtime, then std::visit to get the compile time constant. The downside is that this can generate a lot of code really easily.

template<auto I>
using constant_t=std::integral_constant<decltype(I),I>;
template<auto I>
constexpr constant_t<I> constant_v={};
template<auto...Is>
using var_enum_t=std::variant<constant_t<Is>...>;
template<class Indexes>
struct var_enum_over;
template<class Indexes>
using var_enum_over_t=typename var_enum_over<Indexes>::type;
template<class T,T...ts>
struct var_enum_over<std::integral_sequence<T,Is...>>{
using type=var_enum_t<Is...>;
};
template<std::size_t N>
using var_index_t=var_enum_over_t<std::make_index_sequence<N>>;

template<std::size_t N>
var_index_t<N> var_index(std::size_t I){
constexpr auto table=[]<std::size_t...Is>(std::index_sequence<Is...>)->std::array<N,var_index_t<N>>{
return { var_index_t<N>(constant_v<Is>)..., };
}(std::make_index_sequence<N>{});
if (I>=N) throw 0; // todo: something better
return table[I];
}

(Probably has typos).

Now you can:

auto idx=var_index<5>(3/* 3 can be runtime */);
std::visit([](auto three){
// three is a compile time value here
}, idx);

Using a `constexpr` function on a variable passed as lvalue in a non-constexpr function

Below, I answer as to why your code doesn't work. Focusing on your use-case: as the others have said, std::array::size is not static, and all std::size does is call that non-static function. Your best bet is to simply add a static size function to your Vector class:

static constexpr auto size() {
return D;
}

Your implementation of cross will not work because you cannot use non-constant expressions to initialize templates. See this SO answer on why function arguments are not constant expressions.

Basically, calling your cross function requires generating a new instance of the cross_dispatch structure for every different value of std::size(vec1), which also requires knowing the address of every given vec1 at compile-time since std::size calls a non-static function. You should be able to see from this that the compiler simply can't know which instances of cross_dispatch need to be created.

Above, I provided a solution specific to your use-case. If you were doing more than measuring the size of your Vector, a second solution would involve passing the objects as template parameters instead (which will require them to be static):

template <typename VectorType, VectorType const& vec1, VectorType const& vec2>
constexpr VectorType cross()
{
return cross_dispatch<std::size(vec1), VectorType>::apply(vec1, vec2);
}

int main()
{
static vector p1 {1.,2.,3.};
static vector q1 {1.,2.,3.};

cross<vector, p1, q1>();
}

Because p1 and q1 are static, their addresses can be known at compile-time, allowing cross's template to be initialized. Template parameters do not change at run-time, so std::size(vec1) is now a constant expression.

Passing parameter pack in constexpr

Function arguments are not constexpr expressions (for good reasons) even if part of constexpr or consteval functions.

If you are willing to make Test::size static, independent of objects:

#include <iostream>

template<class T>
class Test
{
public:
T value;
constexpr static size_t size() { return sizeof(T) + 3; }
};

template<typename ...T>
constexpr size_t calc_types()
{
return (T::size() + ...);
}

template<typename ...T>
constexpr size_t calc_vals(const T&...)
{
return calc_types<T...>();
}

template<typename ...T>
constexpr void wrapper_types()
{
static_assert(calc_types<T...>() <= 11, "oops");
}

template<typename ...T>
constexpr void wrapper_vals(const T&...)
{
wrapper_types<T...>();
}

int main()
{
Test<int> a;
Test<char> b;
// a.size() + b.size() == 11

// works
constexpr int v = calc_vals(a, b);
static_assert(v <= 11, "oops");

// wrapper function
wrapper_vals(a, b);
}

Why is my constexpr object not constexpr inside my function?

You need to pass tensor1 as template argument if you want to use it in a context that requires a constant expression. This is currently (C++17) only possible as reference:

template<std::size_t t1_skipPos, std::size_t t2_skipPos, auto& tensor1, auto& tensor2>
constexpr auto contraction(){

auto sris_tensor1 = save_recreated_index_sequence<0, tensor1.indices_amount-2,
t1_skipPos, tensor1.indices_amount, DIM3>(tensor1.calculate_indices());

return sris_tensor1;

}

//...

constexpr auto contract = contraction<0, 0, tensor1, tensor2>();

This does however also require you to declare tensor1 and tensor2 as static, so that they have linkage. Otherwise they cannot be used as template argument and it makes the function unusable with non-constexpr variables.

Note that C++20 adds non-type template parameters of class type which may make the reference, and consequently also the requirement of linkage, unnecessary (by using auto instead of auto&), but that does not apply to your class in the current state of the C++20 draft, which requires all non-static data members of the type to be public. This requirement was recently changed, so current compiler versions might be implementing the less strict requirements of the previous draft version when using -std=c++2a, in which your class would have been valid non-type template parameter of class type.

You cannot use function parameters in a context that requires a constant expression. The fact that this context appears in another constant expression evaluation doesn't matter. A constexpr function must always also be a valid runtime function.


In the specific case of the shown code you can however instead make indices_amount a static member of the class and access that through the type, so that the state of the function parameters is never used in the constant expression context:

static constexpr std::size_t indices_amount = std::tuple_size_v<Args...>;

//...

template<std::size_t t1_skipPos, std::size_t t2_skipPos, typename T1, typename T2>
constexpr auto contraction(T1 tensor1, T2 tensor2){

/*ERROR: tensor1 is not a constant expression*/
auto sris_tensor1 = save_recreated_index_sequence<0, T1::indices_amount-2,
t1_skipPos, T1::indices_amount, DIM3>(tensor1.calculate_indices());

return sris_tensor1;

}

Also note that it seems to make no sense that your tensorBase has a parameter pack as second template parameter. It is only ever filled with exactly one argument and it wouldn't work with any other number either. For example std::tuple_size<Args...> only works if the pack has exactly one element. I suggest you remove all the ...s relating to Args in that class.

Trying to pass a constexpr lambda and use it to explicitly specify returning type

Parameters to constexpr functions are not themselves constexpr objects - so you cannot use them in constant expressions. Both of your examples returning arrays are ill-formed because there is no valid call to them.

To understand why, consider this nonsense example:

struct Z { int i; constexpr int operator()() const { return i; }; };

template <int V> struct X { };
template <typename F> constexpr auto foo(F f) -> X<f()> { return {}; }

constexpr auto a = foo(Z{2});
constexpr auto b = foo(Z{3});

Z has a constexpr call operator, and this is well-formed:

constexpr auto c = Z{3}();
static_assert(c == 3);

But if the earlier usage were allowed, we'd have two calls to foo<Z> that would have to return different types. This could only fly if the actual value f were the template parameter.


Note that clang compiling the declaration is not, in of itself, a compiler error. This is a class of situations that are ill-formed, no diagnostic required.

constexpr for values passed in by reference

A& a is not usable in constant expressions, making your program ill-formed.

The rule forbidding a.b.size() to be a constant expression with a being a A& is the following:

[expr.const]/3


A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is of reference type or of const-qualified integral or enumeration type, and its initializer is a constant initializer.

In your case, the variable a is:

  • not declared constexpr (as a function argument, it wouldn't make sense),
  • and is not an integral or enumeration type,
  • and is not a reference whose initialization is a constant initializer:

[expr.const]/2


A constant initializer for a variable or temporary object o is an initializer for which interpreting its full-expression as a constant-expression results in a constant expression, except that if o is an object, such an initializer may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.


Take the following reduced example:

struct s { constexpr static bool true_value() { return true; } };
void assert_on(s const& ref)
{
static_assert(ref.true_value());
}

int main()
{
assert_on(s{});
}

gcc-9 wrongly accepts it1, but clang-8 produce the right diagnostic:

error: static_assert expression is not an integral constant expression

Full demo: https://godbolt.org/z/t_-Ubj


1) This is GCC bug #66477, active from version 5.1 and yet to be resolved.



Related Topics



Leave a reply



Submit