I've made a function that concatenates multiple smaller values into one larger value while preserving the bianry representation of the values (ex. to build an int argb
from multiple unsigned char r, g, b, a
). I know I can also achive this by bit shifting the values but that's not the matter of this question.
However, if I use the function to actually generate an integer from those values, msvc throws a compiler error:
error C3615: constexpr function 'Color::operator int' cannot result in a constant expression
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of '<lambda_dcb9c20fcc2050e56c066522a838749d>::operator ()'
Here is a complete sample. Clang and gcc compile the code but msvc refuses:
#include <type_traits>
#include <memory>
namespace detail
{
template <typename From, typename To, size_t Size>
union binary_fusion_helper
{
const From from[Size];
const To to;
};
template <typename To, typename Arg, typename ...Args, typename = std::enable_if_t<(... && std::is_same_v<std::remove_reference_t<Arg>, std::remove_reference_t<Args>>)>>
constexpr To binary_fusion(Arg arg, Args... args)
{
using in_t = std::remove_reference_t<Arg>;
using out_t = To;
static_assert(sizeof(out_t) == sizeof(in_t) * (sizeof...(Args) + 1), "The target type must be of exact same size as the sum of all argument types.");
constexpr size_t num = sizeof(out_t) / sizeof(in_t);
return binary_fusion_helper<in_t, out_t, num> { std::forward<Arg>(arg), std::forward<Args>(args)... }.to;
}
}
template <typename To>
constexpr auto binary_fusion = [](auto ...values) -> To
{
return detail::binary_fusion<std::remove_reference_t<To>>(values...);
};
struct Color
{
float r, g, b, a;
explicit constexpr operator int() const noexcept
{
return binary_fusion<int>(static_cast<unsigned char>(r * 255), static_cast<unsigned char>(g * 255),
static_cast<unsigned char>(b * 255), static_cast<unsigned char>(a * 255));
}
};
Do clang and gcc just ignore that the code will never run as a constexpr or is msvc wrong? And if msvc is correct, why can't the function run at compile time?
Every compiler is correct. The rule in [dcl.constexpr]/5 is:
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, a constant initializer for some object ([basic.start.static]), the program is ill-formed, no diagnostic required.
There is no set of arguments you can pass in to binary_fusion
that would allow it to be evaluated as a core constant expression, so declaring it constexpr
is ill-formed, NDR. The reason this is the case is because detail::binary_fusion()
initializes a union with one active member and then reads from the inactive member, which you are not allowed to do in constant expressions ([expr.const]/4.8):
an lvalue-to-rvalue conversion that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;
MSVC somehow diagnoses this, gcc/clang happen not to. All compilers correctly diagnose this:
constexpr Color c{1.0f, 1.0f, 1.0f, 1.0f};
constexpr int i = static_cast<int>(c); // error: not a constant expression