Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSVC constexpr function 'xyz' cannot result in a constant expression

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?

like image 389
Timo Avatar asked Dec 21 '18 16:12

Timo


People also ask

Are constexpr functions Const?

constexpr variables A constexpr variable must be initialized at compile time. All constexpr variables are const . A variable can be declared with constexpr , when it has a literal type and is initialized. If the initialization is performed by a constructor, the constructor must be declared as constexpr .

Can constexpr functions call non constexpr functions?

A call to a constexpr function produces the same result as a call to an equivalent non- constexpr function , except that a call to a constexpr function can appear in a constant expression. The main function cannot be declared with the constexpr specifier.

Is constexpr guaranteed?

A constexpr function that is eligible to be evaluated at compile-time will only be evaluated at compile-time if the return value is used where a constant expression is required. Otherwise, compile-time evaluation is not guaranteed.


1 Answers

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
like image 62
Barry Avatar answered Oct 18 '22 03:10

Barry