Ошибка SFINAE при оценке constexpr в параметре шаблона?

По какой-то причине этот constexpr не корректно оценивается в контексте параметра шаблона:

#include <iostream>
#include <functional>

namespace detail
{
    // Reason to use an enum class rahter than just an int is so as to ensure
    // there will not be any clashes resulting in an ambigious overload.
    enum class enabler
    {
        enabled
    };
}
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), detail::enabler> = detail::enabler::enabled
#define ENABLE_IF_DEFINITION(...) std::enable_if_t<(__VA_ARGS__), detail::enabler>

namespace detail
{
    template <typename T, bool IS_BUILTIN>
    class is_value
    {
        T item_to_find;
        std::function<bool(T const& lhs, T const& rhs)> predicate;
    public:
        constexpr is_value(T item_to_find, std::function<bool(T, T)> predicate)
            : item_to_find(item_to_find)
            , predicate(predicate)
        {}

        constexpr bool one_of() const
        {
            return false;
        }

        template <typename T1, typename...Ts>
        constexpr bool one_of(T1 const & item, Ts const&...args) const
        {
            return predicate(item_to_find, item) ? true : one_of(args...);
        }
    };

    template <typename T>
    class is_value<T, false>
    {
        T const& item_to_find;
        std::function<bool(T const& lhs, T const& rhs)> predicate;
    public:
        constexpr is_value(T const& item_to_find, std::function<bool(T const&, T const&)> predicate)
            : item_to_find(item_to_find)
            , predicate(predicate)
        {}

        constexpr bool one_of() const
        {
            return false;
        }

        template <typename T1, typename...Ts>
        constexpr bool one_of(T1 const & item, Ts const&...args) const
        {
            return predicate(item_to_find, item) ? true : one_of(args...);
        }
    };
}

// Find if a value is one of one of the values in the variadic parameter list.
// There is one overload for builtin types and one for classes.  This is so
// that you can use builtins for template parameters.
//
// Complexity is O(n).
//
// Usage:
//
//   if (is_value(1).one_of(3, 2, 1)) { /* do something */ }
//
template <typename T, ENABLE_IF(!std::is_class<T>::value)>
constexpr auto const is_value(T item_to_find, std::function<bool(T, T)> predicate = [](T lhs, T rhs) { return lhs == rhs; })
{
    return detail::is_value<T, true>(item_to_find, predicate);
}

template <typename T, ENABLE_IF(std::is_class<T>::value)>
constexpr auto const is_value(T const& item_to_find, std::function<bool(T const&, T const&)> predicate = [](T const& lhs, T const& rhs) { return lhs == rhs; })
{
    return detail::is_value<T, false>(item_to_find, predicate);
}


template <int I, ENABLE_IF(is_value(I).one_of(3,2,1))>
    void fn()
{
}

int main()
{
    fn<3>();
    std::cout << "Hello, world!\n" << is_value(3).one_of(3,2,1);
}

Я тестировал это с помощью clang, g++ и vС++. У каждого были разные ошибки:

лязг

source_file.cpp:98:5: error: no matching function for call to 'fn'
    fn<3>();
    ^~~~~
source_file.cpp:92:10: note: candidate template ignored: substitution failure [with I = 3]: non-type template argument is not a constant expression
    void fn()
         ^
1 error generated.

г ++

source_file.cpp: In function ‘int main()’:
source_file.cpp:98:11: error: no matching function for call to ‘fn()’
     fn<3>();
...

VС++

source_file.cpp(91): fatal error C1001: An internal error has occurred in the compiler.
(compiler file 'msc1.cpp', line 1421)
...

Является ли мой код недействительным или компиляторы еще не выполнили задание?

Ответ 1

Ваш код недействителен. Компилятор (GCC7.1 для меня) предоставляет полезную ошибку, которая позволяет решить эту проблему.

Вопрос:

...\main.cpp|19|note: 'detail::is_value<int, true>' is not literal because:|
...\main.cpp|19|note:   'detail::is_value<int, true>' has a non-trivial destructor|

Причина detail::is_value не имеет тривиального деструктора из-за члена std::function<>; std::function<> может выполнять динамическое распределение памяти (среди других причин), поэтому это не тривиально. Вы должны заменить его на тривиально разрушаемый тип; Я представляю простое решение ниже.

Примечание. Даже если std::function<> было тривиально разрушаемым, его operator(), похоже, не объявляется как constexpr (см. http://en.cppreference.com/w/cpp/utility/functional/function/operator()), поэтому он не будет работать.

Пример рабочего кода (при необходимости приспособитесь):

#include <iostream>
#include <functional>

namespace detail
{
    // Reason to use an enum class rahter than just an int is so as to ensure
    // there will not be any clashes resulting in an ambigious overload.
    enum class enabler
    {
        enabled
    };
}
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), detail::enabler> = detail::enabler::enabled
#define ENABLE_IF_DEFINITION(...) std::enable_if_t<(__VA_ARGS__), detail::enabler>

namespace detail
{
    // notice the new template parameter F
    template <typename T, typename F, bool IS_BUILTIN>
    class is_value
    {
        T item_to_find;
        F predicate;
    public:
        constexpr is_value(T item_to_find, F predicate)
            : item_to_find(item_to_find)
            , predicate(predicate)
        {}

        constexpr bool one_of() const
        {
            return false;
        }

        template <typename T1, typename...Ts>
        constexpr bool one_of(T1 const & item, Ts const&...args) const
        {
            return predicate(item_to_find, item) ? true : one_of(args...);
        }
    };

    template <typename T, typename F>
    class is_value<T, F, false>
    {
        T const& item_to_find;
        F predicate;
    public:
        constexpr is_value(T const& item_to_find, F predicate)
            : item_to_find(item_to_find)
            , predicate(predicate)
        {}

        constexpr bool one_of() const
        {
            return false;
        }

        template <typename T1, typename...Ts>
        constexpr bool one_of(T1 const& item, Ts const&... args) const
        {
            return predicate(item_to_find, item) ? true : one_of(args...);
        }
    };
}

// sample predicate
template<class T>
struct default_compare
{
    constexpr bool operator()(T const& lhs, T const& rhs) const
    noexcept(noexcept(std::declval<T const&>() == std::declval<T const&>()))
    {
        return lhs == rhs;
    }
};

// Find if a value is one of one of the values in the variadic parameter list.
// There is one overload for builtin types and one for classes.  This is so
// that you can use builtins for template parameters.
//
// Complexity is O(n).
//
// Usage:
//
//   if (is_value(1).one_of(3, 2, 1)) { /* do something */ }
//
template <typename T, typename F = default_compare<T>, ENABLE_IF(!std::is_class<T>::value)>
constexpr auto const is_value(T item_to_find, F predicate = {})
{
    return detail::is_value<T, F, true>(item_to_find, predicate);
}

template <typename T, typename F = default_compare<T>, ENABLE_IF(std::is_class<T>::value)>
constexpr auto const is_value(T const& item_to_find, F predicate = {})
{
    return detail::is_value<T, F, false>(item_to_find, predicate);
}

template <int I, ENABLE_IF(is_value(I).one_of(3,2,1))>
    void fn()
{
}

int main()
{
    fn<3>();
    std::cout << "Hello, world!\n" << is_value(3).one_of(3,2,1);
}