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