SFINAE and noexcept specifier

2019-04-06 09:35发布

问题:

Does an expression in noexcept specifier's parentheses participate in SFINAE during overload resolution of function templates?

I want to make an wrapper for aggregates and want the std::is_constructible predicate to work properly for it:

template< typename type >
struct embrace
    : type
{

    template< typename ...arguments >
    embrace(arguments &&... _arguments) noexcept(noexcept(type{std::forward< arguments >(_arguments)...}))
        : type{std::forward< arguments >(_arguments)...} // braces 
    { ; }

};

int
main()
{
    struct S { int i; double j; }; // aggregate
    using E = embrace< S >;
    E b(1, 1.0); // "parentheses"-constructible => can be used as usual types
    b.i = 1; b.j = 2.0; // accessible
    static_assert(std::is_constructible< E, int, double >{});
    static_assert(std::is_constructible< E, struct B >{}); // want hard error here
    return EXIT_SUCCESS;
}

But my attempt to use noexcept operator inside noexcept specification to enable SFINAE is failed, and the templated constructor accepts everything passed to it. How can the constructor be restricted?

It is not permitted by the Standard to specialize any predicates from <type_traits>. How to deal with c-tors that accepts variadic template parameter packs and SFINAE in general? Is there an impasse and inherent language flaw?

回答1:

SFINAE simply doesn't apply to exception-specifications, whether or not noexcept is part of the function type.

See the note in [temp.deduct]/7:

The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. [ Note: The equivalent substitution in exception specifications is done only when the exception-specification is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. —end note ]

P0012R1 didn't change anything in this respect.

Piotr's answer covers the fix for your code.



回答2:

How can the constructor be restricted?

#include <utility>

template <typename type>
struct embrace : type
{
    template <typename... arguments
            , typename = decltype(type{std::declval<arguments>()...})>
    embrace(arguments&&... _arguments)
        noexcept(noexcept(type{std::forward<arguments>(_arguments)...}))
        : type{std::forward<arguments>(_arguments)...}
    {
    }
};

DEMO

(or shorter):

#include <utility>

template <typename type>
struct embrace : type
{
    template <typename... arguments
            , bool NoExcept = noexcept(type{std::declval<arguments>()...})>
    constexpr
    embrace(arguments&&... _arguments)
        noexcept(NoExcept)
        : type{std::forward<arguments>(_arguments)...}
    {
    }
};

DEMO 2



回答3:

Does an expression in noexcept specifier's parentheses participate in SFINAE during overload resolution of function templates?

It doesn't participate in template deduction because the noexcept specifier is not part of a function's type.

The noexcept-specification is not a part of the function type. (until C++17)

Source

Therefore, when your template parameter type is deduced, noexcept is not part of the deduced type. Your compiler seems to return true for any type which is why you aren't able to detect whether it is noexcept or not; that is why everything is accepted.

I ran into the same issue. You can check my question/answer here:

How can I detect whether a template argument is a noexcept function?

Basically, your only option is to wait for a C++17 compliant compiler.