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