Detecting constexpr with SFINAE

前端 未结 2 1342
無奈伤痛
無奈伤痛 2020-11-27 04:57

I\'m working on upgrading some C++ code to take advantage of the new functionality in C++11. I have a trait class with a few functions returning fundamental types which woul

相关标签:
2条回答
  • 2020-11-27 05:38

    NOTE: I opened a question here about whether OPs code is actually valid. My rewritten example below will work in any case.


    but I would like to know if the code is legal C++11

    It is, although the default template argument may be considered a bit unusual. I personally like the following style better, which is similar to how you (read: I) write a trait to check for a function's existence, just using a non-type template parameter and leaving out the decltype:

    #include <type_traits>
    
    namespace detail{
    template<int> struct sfinae_true : std::true_type{};
    template<class T>
    sfinae_true<(T::f(), 0)> check(int);
    template<class>
    std::false_type check(...);
    } // detail::
    
    template<class T>
    struct has_constexpr_f : decltype(detail::check<T>(0)){};
    

    Live example.


    Explanation time~

    Your original code works because a default template argument's point of instantiation is the point of instantiation of its function template, meaning, in your case, in main, so it can't be substituted earlier than that.

    §14.6.4.1 [temp.point] p2

    If a function template [...] is called in a way which uses the definition of a default argument of that function template [...], the point of instantiation of the default argument is the point of instantiation of the function template [...].

    After that, it's just usual SFINAE rules.


    † Atleast I think so, it's not entirely clear in the standard.

    0 讨论(0)
  • 2020-11-27 05:40

    Prompted by @marshall-clow, I put together a somewhat more-generic version of an type-trait for detecting constexpr. I modelled it on std::invoke_result, but because constexpr depends on the inputs, the template arguments are for the values passed in, rather than the types.

    It's somewhat limited, as the template args can only be a limited set of types, and they're all const when they get to the method call. You can easily test a constexpr wrapper method if you need other types, or non-const lvalues for a reference parameter.

    So somewhat more of an exercise and demonstration than actually-useful code.

    And the use of template<auto F, auto... Args> makes it C++17-only, needing gcc 7 or clang 4. MSVC 14.10.25017 can't compile it.

    namespace constexpr_traits {
    
    namespace detail {
    
    // Call the provided method with the provided args.
    // This gives us a non-type template parameter for void-returning F.
    // This wouldn't be needed if "auto = F(Args...)" was a valid template
    // parameter for void-returning F.
    template<auto F, auto... Args>
    constexpr void* constexpr_caller() {
        F(Args...);
        return nullptr;
    }
    
    // Takes a parameter with elipsis conversion, so will never be selected
    // when another viable overload is present
    template<auto F, auto... Args>
    constexpr bool is_constexpr(...) { return false; }
    
    // Fails substitution if constexpr_caller<F, Args...>() can't be
    // called in constexpr context
    template<auto F, auto... Args, auto = constexpr_caller<F, Args...>()>
    constexpr bool is_constexpr(int) { return true; }
    
    }
    
    template<auto F, auto... Args>
    struct invoke_constexpr : std::bool_constant<detail::is_constexpr<F, Args...>(0)> {};
    
    }
    

    Live demo with use-cases on wandbox

    0 讨论(0)
提交回复
热议问题