I tried to extract a minimal working example from my codebase:
#include <concepts>
enum class unit_type
{
};
template <typename Candidate>
concept unit_c = requires()
{
requires std::semiregular<Candidate>;
{ Candidate::type } -> std::same_as<const unit_type&>;
};
struct unit
{
static constexpr unit_type type{};
};
template<unit_c auto unit_1, unit_c auto unit_2>
struct unit_product
{
static constexpr unit_type type{};
};
template <unit_c Unit1, unit_c Unit2>
consteval unit_c auto operator*(Unit1 unit_1, Unit2 unit_2) noexcept
{
return unit_product<unit_1, unit_2>{};
}
int main()
{
constexpr unit_c auto a = unit{} * unit{};
return 0;
}
The above code compiles fine. but why? I thought one cannot pass the parameters of a consteval function to a template. All gcc, clang and msvc all accept the above code.
This code produces an error:
template <int val>
struct bla
{
};
consteval void bla_test(int val)
{
bla<val>;
}
What is the key difference between those two cases?
Copying a unit
object (with the defaulted copy constructor) does not actually access that object (since it has no members). So, creating a copy to use as the argument for the NTTP is a constant expression even if the initial unit
object isn't a constant expression.
It is a similar thing to calling a member function that doesn't access *this
:
unit a;
constexpr unit b = a; // a isn't accessed
std::array<int, 5> arr;
constexpr std::size_t sz = arr.size(); // arr isn't accessed
The key difference with the int
case is that the value of int val
would be accessed if you tried to copy it.