0

I have the following template / compile-time utility helper functions. It works fine in all three compilers (MSVC, GCC, clang) when everything is constexpr, but changing a few bits to consteval results in an odd error in MSVC. I want to migrate all my constexpr machinery to consteval as much as possible, and this problem is not helping.

#include <optional>
#include <tuple>
#include <type_traits>
 
template <auto v>
struct value_as_type {
    static constexpr auto value = v;
    using type = decltype(value);

    consteval operator type() const noexcept {
        return v;
    }
};

template <size_t First, size_t Last, typename Functor>
consteval void consteval_for([[maybe_unused]] Functor&& f) noexcept
{
    if constexpr (First < Last)
    {
        if constexpr (std::is_same_v<bool, std::invoke_result_t<Functor()>>)
        {
            if (f(value_as_type<First>{}) == false)
                return;
        }
        else
            f(value_as_type<First>{});

        consteval_for<First + 1, Last>(std::forward<Functor>(f));
    }
}

template <size_t index, typename... Args>
using type_by_index = std::tuple_element_t<index, std::tuple<Args...>>;

template <typename T, typename... Args>
[[nodiscard]] consteval std::optional<size_t> index_for_type() noexcept
{
    std::optional<size_t> index;
    consteval_for<0, sizeof...(Args)>([&index](auto i) {
        if (!index) // The index of the first occurrence is stored
        {
            if constexpr (std::is_same_v<T, type_by_index<static_cast<size_t>(i), Args... >> )
                index = static_cast<size_t>(i);
        }
    });

    return index;
}

static_assert(index_for_type<int, float, void, int, bool>() == 2);

The error message is:

<source>(52): error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'std::optional<size_t>'        
<source>(52): note: Invalid aggregate initialization

I'm not seeing any aggregate initialization of the optional at all. And my question is: do you think it's a bug in MSVC, or is there something not right with my code?

P. S. Removing optional and just returning size_t removes the error, but I do need a way to say "the type is not present in the pack" without compilation error. std::optional seems the perfect fit for this semantically.

Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335
  • What line is the error? – Ben Voigt Jul 13 '21 at 22:06
  • @BenVoigt: the one with `static_assert`. Not helpful at all. – Violet Giraffe Jul 13 '21 at 22:09
  • Have you tested the individual pieces (`value_as_type`, `consteval_for`, `type_by_index`) separately using static_assert? – Ben Voigt Jul 13 '21 at 22:12
  • `value_as_type` doesn't seem necessary or useful. Does it work if that layer is eliminated? – Ben Voigt Jul 13 '21 at 22:15
  • It also seems like the lambda would be much simplified if the parameter type were `size_t` instead of `auto`. – Ben Voigt Jul 13 '21 at 22:21
  • @BenVoigt: that change does not make a difference - the same compilation error remains. https://godbolt.org/z/11brj9cKx – Violet Giraffe Jul 13 '21 at 22:22
  • value_as_type is quite necessary, without it I'm getting a "non-type template argument is not a constant expression" error with all three compilers here: type_by_index(i), Args... >>. The way I understand it, the constexpr-ness of i is lost when it's passed into a lambda as an argument, and I was originally writing this in C++17 when we didn't have template lambdas. Using `auto` and extracting the value from the argument's type (rather than the value itself) re-establishes the known-at-compile-time value. Now a more transparent way of doing this would be a template lambda. – Violet Giraffe Jul 13 '21 at 22:29
  • 1
    You might look into eliminating `value_as_type` in favor of `std::integral_constant` – Ben Voigt Jul 13 '21 at 22:45

1 Answers1

3

The same error occurs with all the guts removed.

#include <optional>
 
[[nodiscard]] consteval std::optional<size_t> index_for_type() noexcept
{
    return 1;
}

static_assert(index_for_type() == 2);

This leaves me to believe that Microsoft's std::optional is simply not consteval-ready yet.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I see! It didn't occur to me this could be that simple. Looks like a compiler bug, right? Should I report this as such? – Violet Giraffe Jul 13 '21 at 22:35
  • 1
    I would definitely report it. Whether it is a compiler problem or a library problem, the Visual C++ team will figure out where the fix needs to go. – Ben Voigt Jul 13 '21 at 22:36
  • @VioletGiraffe: I think I've reached the point of optimal simplification now. There is nothing even a little bit tricky left except `consteval` and a `std::optional` return type. – Ben Voigt Jul 13 '21 at 22:39
  • @VioletGiraffe: Turns out it does not even have to be a template function, you still get the error. – Ben Voigt Jul 13 '21 at 22:42