SFINAE failing when evaluating a constexpr in a te

2019-06-24 04:02发布

问题:

For some reason, this constexpr is not being evaluated correctly in a template parameter context:

#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);
}

I've tested this with clang, g++ and vc++. Each had different errors:

clang

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.

g++

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

vc++

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

Is my code invalid or are the compilers just not up to the job yet?

回答1:

Your code is invalid. The compiler (GCC7.1 for me) provides helpful error that allows us to solve this problem.

Issue:

...\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|

The reason detail::is_value does not have a trivial destructor is due to the std::function<> member; std::function<> might perform dynamic memory allocation (among other reasons), thus it is not trivial. You have to replace it with a trivially destructible type; I present a simple solution below.

Note: Even if std::function<> was trivially destructible, its operator() does not seem to be declared as constexpr (see: http://en.cppreference.com/w/cpp/utility/functional/function/operator()), so it would not work either.

Sample working code (adapt as needed):

#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);
}