As a follow up of this question, I tested the behavior of both clang and gcc. It appears that the two compiler have a different interpretation of the c++ standard.
In the example below, GCC refuses to compile, if a non copyable argument would need to be copied according to the deduction guide hypothetical constructor argument. Clang does not perform this check:
#include <cstddef>
struct not_copyable{
not_copyable()=default;
not_copyable(const not_copyable&)=delete;
};
struct movable{
movable()=default;
movable(movable&&);
};
template <typename T, size_t N>
struct A
{ template <typename ... Ts> A (Ts const & ...) {} };
template <typename T, size_t N>
struct B
{ template <typename ... Ts> B (const Ts & ...) {} };
template <typename T, typename ... Ts>
A(T const &, Ts const & ...) -> A<T, 1U + sizeof...(Ts)>;
template <typename T, typename ... Ts>
B(T, Ts ...) -> B<T, 1 + sizeof...(Ts)>;
int main()
{
not_copyable nc;
movable m;
auto a0 = A{nc}; // gcc & clang -> compile
auto a1 = A{m}; // gcc & clang -> compile
auto b0 = B{nc}; // clang ->compile; gcc -> error
auto b1 = B{m}; // clang ->compile; gcc -> error
}
In think the right behavior is defined in this paragraph of the C++ standard [over.match.class.deduct]/2:
Initialization and overload resolution are performed as described in [dcl.init] and [over.match.ctor], [over.match.copy], or [over.match.list] (as appropriate for the type of initialization performed) for an object of a hypothetical class type, where the selected functions and function templates are considered to be the constructors of that class type for the purpose of forming an overload set,[...]
I emphasized "for the purpose of forming an overload set" because I think this is where clang and gcc diverge. Clang does not seem to check if the deduction guide hypothetical constructor is a viable function, but gcc does. Which compiler is right?
Clang does not seem to check if the deduction guide hypothetical constructor is a viable function, but gcc does.
Actually, the deduction guide is a viable function. A function being viable just means that the number of arguments matches, the constraints are satisfied, and you can form implicit conversion sequences for each parameter/argument pair. And when we're checking if an ICS exists, [over.best.ics]/2:
Other properties, such as the lifetime, storage class, alignment, accessibility of the argument, whether the argument is a bit-field, and whether a function is deleted, are ignored.
It's very important that deleting a function does not make it non-viable, because it's important that it can still end up being the best viable candidate. This means that the fact that not_copyable
's copy constructor is deleted should only come into effect when we're actually invoking it.
For example, both gcc and clang reject this program. #1
is a viable candidate, and it's the best viable candidate, despite the deleted copy constructor:
struct NC {
NC() = default;
NC(NC const&) = delete;
NC& operator=(NC const&) = delete;
};
void foo(NC ); // #1
template <typename T> void foo(T const&); // #2
int main() {
NC nc;
foo(nc);
}
But we're never actually invoking the synthesized functions and function templates that we use for deduction. We're just performing overload resolution and selecting the best candidate - which we're only using to pick the class type, and then we start over. At no point should we actually require copying.
I think this is a gcc bug. Filed 86439.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With