Date: Sat, 18 May 2024 15:18:09 +0100

At some point we're going to have to make consteval functions

compatible with template metaprogramming, so let's talk about the

possible ways of doing that.

First of all, let me start with an example of compile-time Factorial

as a template struct:

template<unsigned n>

struct Factorial {

inline static constexpr unsigned value = n * Factorial<n-1u>::value;

};

template<>

struct Factorial<0u> {

inline static constexpr unsigned value = 1u;

};

It's possible to rewrite this template struct as a consteval function

as follows:

consteval unsigned Factorial(unsigned const n)

{

if ( 0u == n ) return 1u;

return n * Factorial(n-1u);

}

Now let's try get the template to use the consteval function. The

following works fine:

consteval unsigned FactorialC(unsigned const n)

{

if ( 0u == n ) return 1u;

return n * FactorialC(n-1u);

}

template<unsigned n>

struct FactorialT {

inline static constexpr unsigned value = FactorialC(n);

};

Now let's try to do it the other way around. Let's try get the

consteval function to make use of the template:

template<unsigned n>

struct FactorialT {

inline static constexpr unsigned value = n * FactorialT(n-1u);

};

template<>

struct FactorialT<0u> {

inline static constexpr unsigned value = 1u;

};

consteval unsigned FactorialC(unsigned const n)

{

return FactorialT<n>(); // compiler error

}

This fails to compile because the function argument 'n' -- even though

it's a compile-time constant -- cannot be used to instantiate a

template. I can think of two possible remedies to this problem. The

first one is very simple:

- - - Remedy 1: Inside the body of a consteval function, allow all

compile-time constants (including function arguments) to be used to

instantiate templates.

Remedy 1 would probably be the easiest and best solution. An

alternative would be:

- - - Remedy 2: Allow the use of a deduction guide in order to treat a

template as though it were a consteval function, and so then the body

of the consteval function uses the deduction guide rather than using

the template directly.

So in order to treat the Factorial template struct as though it's a

consteval function, you would use a deduction guide something like:

consteval unsigned Monkey(unsigned n) = FactorialT<n>::value;

And so then FactorialC would become:

consteval unsigned FactorialC(unsigned const n)

{

return Monkey(n);

}

I can't right now think of any reason why Remedy 2 would be chosen

over Remedy 1 -- unless Remedy 1 would just be too much hassle for

compiler vendors to implement, or if Remedy 1 would mean too many

alterations to other parts of the Standard.

Can anyone think of any other remedies?

compatible with template metaprogramming, so let's talk about the

possible ways of doing that.

First of all, let me start with an example of compile-time Factorial

as a template struct:

template<unsigned n>

struct Factorial {

inline static constexpr unsigned value = n * Factorial<n-1u>::value;

};

template<>

struct Factorial<0u> {

inline static constexpr unsigned value = 1u;

};

It's possible to rewrite this template struct as a consteval function

as follows:

consteval unsigned Factorial(unsigned const n)

{

if ( 0u == n ) return 1u;

return n * Factorial(n-1u);

}

Now let's try get the template to use the consteval function. The

following works fine:

consteval unsigned FactorialC(unsigned const n)

{

if ( 0u == n ) return 1u;

return n * FactorialC(n-1u);

}

template<unsigned n>

struct FactorialT {

inline static constexpr unsigned value = FactorialC(n);

};

Now let's try to do it the other way around. Let's try get the

consteval function to make use of the template:

template<unsigned n>

struct FactorialT {

inline static constexpr unsigned value = n * FactorialT(n-1u);

};

template<>

struct FactorialT<0u> {

inline static constexpr unsigned value = 1u;

};

consteval unsigned FactorialC(unsigned const n)

{

return FactorialT<n>(); // compiler error

}

This fails to compile because the function argument 'n' -- even though

it's a compile-time constant -- cannot be used to instantiate a

template. I can think of two possible remedies to this problem. The

first one is very simple:

- - - Remedy 1: Inside the body of a consteval function, allow all

compile-time constants (including function arguments) to be used to

instantiate templates.

Remedy 1 would probably be the easiest and best solution. An

alternative would be:

- - - Remedy 2: Allow the use of a deduction guide in order to treat a

template as though it were a consteval function, and so then the body

of the consteval function uses the deduction guide rather than using

the template directly.

So in order to treat the Factorial template struct as though it's a

consteval function, you would use a deduction guide something like:

consteval unsigned Monkey(unsigned n) = FactorialT<n>::value;

And so then FactorialC would become:

consteval unsigned FactorialC(unsigned const n)

{

return Monkey(n);

}

I can't right now think of any reason why Remedy 2 would be chosen

over Remedy 1 -- unless Remedy 1 would just be too much hassle for

compiler vendors to implement, or if Remedy 1 would mean too many

alterations to other parts of the Standard.

Can anyone think of any other remedies?

Received on 2024-05-18 14:18:19