Unexpectedly missing implicitly declared copy/move constructors - c++

Consider the following:
#include <type_traits>
template <typename>
struct F;
template <typename R, typename... As>
struct F<R(As...)>
{
template <typename F_, std::enable_if_t<
std::is_invocable_r_v<R, std::decay_t<F_>, As...>>*...>
F(F_&&) {}
F() = default;
template <typename... As_, std::enable_if_t<
std::is_invocable_v<void(As&&...), As_...>>*...>
R operator()(As_&&...)
{ return R(); }
};
struct C
{
F<C()> f_;
// C(C&&) = default; // <<< 1
};
int main() {
F<C(int)> x;
auto y = x;
}
gcc 7.3.0 fails to compile it (deep within std::is_invocable_r):
error: invalid use of incomplete type
‘struct std::__or_<std::is_void<C>, std::is_convertible<C, C> >’
as does clang 5.0.1:
error: no type named 'type' in
'std::__or_<std::is_void<C>, std::is_convertible<C, C> >'
From this I deduce that C is missing move and copy constructors. Indeed, if we uncomment its move constructor (1), this code compiles.
I believe the requirements for them to be implicitly declared are satisfied. Why aren't they?

My best guess is that here:
F<C()> f_;
C is an incomplete type. Within F, C substitutes the template parameter R, which is then used as a template argument of std::is_invocable_r_v. The Standard does not allow to use incomplete types as template arguments of std::is_invocable_r_v and it results in undefined behavior. Undefined behavior includes, among others, arbitrary behavior of a compiler during compilation.
Note that I am not completely sure about my answer, mainly because neither the templated F::F constructor nor its templated operator() is instantiated.

Related

Why does this SFINAE not work with enable_if when one conditional branch is inherited from the base class?

#include <bits/stdc++.h>
#include <type_traits>
// Type your code here, or load an example.
template <typename Types>
class C1 {
public:
using A=typename Types::A;
using B=typename Types::B;
template <typename Dummy = void>
inline typename std::enable_if<std::is_same<A, B>::value, Dummy>::type f() { }
};
template <typename Types>
class C2 : public C1<Types> {
public:
using A=typename Types::A;
using B=typename Types::B;
template <typename Dummy = void>
inline typename std::enable_if<!std::is_same<A, B>::value, Dummy>::type f() { }
};
template <typename Types>
class C3 : public C2<Types> {
public:
using A=typename Types::A;
using B=typename Types::B;
};
struct Types{
using A = int;
using B = int;
};
int main() {
C3<Types> c;
c.f();
return 0;
}
When I try to compile the above code when A and B are not same, I get the following error:
<source>: In function 'int main()':
<source>:42:9: error: no matching function for call to 'C3<Types>::f()'
42 | c.f();
| ^
<source>:23:77: note: candidate: 'template<class Dummy> typename std::enable_if<(! std::is_same<typename Types::A, typename Types::B>::value), Dummy>::type C2<Types>::f() [with Dummy = Dummy; Types = Types]'
23 | inline typename std::enable_if<!std::is_same<A, B>::value, Dummy>::type f() { }
| ^
<source>:23:77: note: template argument deduction/substitution failed:
<source>: In substitution of 'template<class Dummy> typename std::enable_if<false, Dummy>::type C2<Types>::f<Dummy>() [with Dummy = void]':
<source>:42:9: required from here
<source>:23:77: error: no type named 'type' in 'struct std::enable_if<false, void>'
Note that the code I have presented is not the exact code I use but a minimal reproducible example
EDIT: Put up a minimal reproducible example using godbolt in place of the earlier for a better understanding of the situation
As always with such problems, it falls down to the very definition of SFINAE. The S stands for "substitution", which happens into the template that we are trying to instantiate. That template is the member f, and not C.
Even though C is a template also, and both A and B are dependent types in C, they are not dependent type when f is instantiated. They are already known. As such, the condition std::is_same<A, B>::value is not value dependent on any template parameter of f. It doesn't depend on substation into f. This trips the following clause in the C++11 standard (taken from the last draft prior to publication):
[temp.res] (emphasis mine)
8 Knowing which names are type names allows the syntax of every
template definition to be checked. No diagnostic shall be issued for a
template definition for which a valid specialization can be generated.
If no valid specialization can be generated for a template definition, and that template is not instantiated, the template
definition is ill-formed, no diagnostic required.
This means that whatever Types is, if it doesn't uphold the condition of f, then the very definition of f (without even being instatiated), is already ill-formed whenever C is instantiated. A diagnostic is not required for this in general (since checking it is intractable in the general case), but compilers can diagnose it early often enough, and will tell you about the problem.
Now, as to how to fix it, just make the condition of f value dependent on its own template parameter. A simple re-write can be
template <bool Dummy = std::is_same<A, B>::value>
inline auto f(vector<int>& ctx, const string& r) ->
typename std::enable_if<Dummy>::type { }
Now the condition depends on substation in the correct context.
Of course, even if you fix the SFIANE problem, you still need to make sure the overload set is composed of the correct members. The f in C2 hides the f in C1. Add a using declaration to C2 so it's still a candidate
using C1<Types>::f;

Is a diagnostic required for unused member templates with ill formed default template parameters?

Consider the following class template:
template<typename T>
struct S
{
template<auto = T()>
void f();
};
Is it ill formed to instantiate S with template parameters T, for which auto = T() is ill formed?
int main()
{
S<int> a; // ok
S<int&> b; // error
S<int()> c; // gcc ok, clang error
}
This seems to be the case, but the issue is with c, where S is instantiated with a function type. gcc is ok with this, while clang says:
error: cannot create object of function type 'int ()'
which makes sense. Since gcc does diagnose the instantiation with int&, I suspect this is a gcc bug. Is that right, or is a diagnostic not required for this code?
This is CWG1635:
1635. How similar are template default arguments to function default arguments?
Default function arguments are instantiated only when needed. Is the same true of default template arguments? For example, is the following well-formed?
#include <type_traits>
template<class T>
struct X {
template<class U = typename T::type>
static void foo(int){}
static void foo(...){}
};
int main(){
X<std::enable_if<false>>::foo(0);
}
Also, is the effect on lookup the same? E.g.,
struct S {
template<typename T = U> void f();
struct U {};
};
Additional note (November, 2020):
Paper P1787R6, adopted at the November, 2020 meeting, partially addresses this issue.

Unnamed class/typename in template arguments

I was looking through the documentation of SFINAE and there was this template declaration:
template<typename SomeType>
struct inner_type { typedef typename SomeType::type type; };
template <
class T,
class = typename T::type, // SFINAE failure if T has no member type
class U = typename inner_type<T>::type // hard error if T has no member type
// (guaranteed to not occur as of C++14)
> void foo(int) {}
Specifically, I'm asking about class = typename T::type. What's the point of declaring an unnamed class?
Because of the comment I thought that this will result in a compiler error when T doesn't have a member type, but that isn't the case, as foo<int, int, int>(0); compiles fine.
On the other hand
template<class T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
void foo(T t) {}
doesn't compile if T is signed, and compiles if T is unsigned.
What am I missing here?
foo<int, int, int>(0); compiles fine.
Because you specify the 2nd template argument, then the default template argument (i.e. typename T::type) won't be used, then won't trigger compile error.
If you just write foo<int>(0); to make the default template argument to be used, compile will fail.
LIVE
And it's same for your 2nd sample too.
What's the point of declaring an unnamed class?
Because the template parameter won't be used for the template implementation.

decltype(auto) in member function ignores invalid body, decltype(expr) fails

I have a simple templated wrapper struct with a member function calling .error() on an object of its template type.
template <typename T>
struct Wrapper {
T t;
decltype(auto) f() {
return t.error(); // calls .error()
}
};
If I instantiate this with a type that doesn't have an error() member function, it's fine as long as I don't call it. This is the behavior I want.
Wrapper<int> w; // no problem here
// w.error(); // uncommented causes compilation failure
If I use what I thought was the semantic equivalent with a trailing return type, it errors on the variable declaration
template <typename T>
struct Wrapper {
T t;
auto f() -> decltype(t.error()) {
return t.error();
}
};
Wrapper<int> w; // error here
I'll accept that the two are not semantically equivalent, but is there anyway to get the behavior of the former using a trailing return type (C++11 only) without specializing the whole class with some kind of HasError tmp trickery?
The difference between the versions
decltype(auto) f();
auto f() -> decltype(t.error());
is that the function declaration of the second can be invalid. Return type deduction for function templates happens when the definition is instantiated [dcl.spec.auto]/12. Although I could not find anything about return type deduction for member functions of class templates, I think they behave similarly.
Implicitly instantiating the class template Wrapper leads to the instantiation of the declarations, but not of the definitions of all (non-virtual) member functions [temp.inst]/1. The declaration decltype(auto) f(); has a non-deduced placeholder but is valid. On the other hand, auto f() -> decltype(t.error()); has an invalid return type for some instantiations.
A simple solution in C++11 is to postpone the determination of the return type, e.g. by turning f into a function template:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() );
The definition of that function worries me a bit, though:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() )
{
return t.error();
}
For the specializations of Wrapper where t.error() is not valid, the above f is a function template that cannot produce valid specializations. This could fall under [temp.res]/8, which says that such templates are ill-formed, No Diagnostic Required:
If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic
required.
However, I suspect that rule has been introduced to allow, but not require, implementations to check for errors in non-instantiated templates. In this case, there is no programming error in the source code; the error would occur in instantiations of the class template described by the source code. Therefore, I think it should be fine.
An alternative solution is to use a fall-back return type to make the function declaration well-formed even if the definition is not (for all instantiations):
#include <type_traits>
template<typename T> struct type_is { using type = T; };
template <typename T>
struct Wrapper {
T t;
template<typename U=T, typename=void>
struct error_return_type_or_void : type_is<void> {};
template<typename U>
struct error_return_type_or_void
<U, decltype(std::declval<U&>().error(), void())>
: type_is<decltype(std::declval<U&>().error())> {};
auto f() -> typename error_return_type_or_void<>::type {
return t.error();
}
};
One approach is a tagged crtp.
// todo:
template<class T>
struct has_error; // true_type if T.error() is valid
template<class D,class T,bool Test=has_error<T>{}>
struct do_whatever {
D* self(){return static_cast<D*>(this);}
D const* self()const{return static_cast<D const*>(this);}
auto f()->decltype(self()->t.error()) {
return self()->t.error();
}
};
template<class D,class T>
struct do_whatever<D,T,false>{};
now f() just isn't there if T has no error().

trait to drop const from a member function type?

When T is double(float)const I get this error when I try to use function<T>.
implicit instantiation of undefined template 'std::function<double (float) const>'
But it's OK when T is double(float). I tried to use std:: remove_cv<T>::type to remove this const, but that doesn't work. And yes, I have #include<functional>.
So my main question is: How to fix this and remove const so that I can put this function type into std:: function.?
I came across this issue when working with the operator() method of lambdas, but I think this question is generally about any method type, not just for lambdas
But my second question is: What does double(float)const even mean ?!! I can understand
double (ClassName::) (float) const
as it means the member function cannot modify its ClassName object. When I put this type into a template to remove the class type, then I get the double(float)const which is causing trouble.
template<typename>
struct DropClassType;
template<typename Sig, typename C>
struct DropClassType<Sig (C::*)> {
typedef Sig type_without_class;
};
(clang 3.4.2. The errors from g++-4.9.1 are more cryptic, but basically the same)
Why did I get the "implicit instantiation of undefined template" error?
std::function is defined as an undefined base template and a partial specialization that matches "normal" function types (§20.9.11.2 [func.wrap.func]):
template<class> class function; // undefined
template<class R, class... ArgTypes>
class function<R(ArgTypes...)> { /* ... */ };
double (float) const doesn't match R(ArgTypes...), so you get the undefined base template instead.
How to fix this and remove const so that I can put this function type into std::function?
The standard partial specialization trick. While we are at it, let's also remove volatile.
template<class> class rm_func_cv; // undefined
template<class R, class... ArgTypes>
class rm_func_cv<R(ArgTypes...)> { using type = R(ArgTypes...); };
template<class R, class... ArgTypes>
class rm_func_cv<R(ArgTypes...) const> { using type = R(ArgTypes...); };
template<class R, class... ArgTypes>
class rm_func_cv<R(ArgTypes...) volatile> { using type = R(ArgTypes...); };
template<class R, class... ArgTypes>
class rm_func_cv<R(ArgTypes...) const volatile> { using type = R(ArgTypes...); };
Similar tricks can be used to remove ref-qualifiers, of course.
What does double (float) const even mean ?!!
This is a rather obscure corner of the standard (§8.3.5 [dcl.fct]/p6):
A function type with a cv-qualifier-seq or a ref-qualifier (including
a type named by typedef-name (7.1.3, 14.1)) shall appear only as:
the function type for a non-static member function,
the function type to which a pointer to member refers,
the top-level function type of a function typedef declaration or alias-declaration,
the type-id in the default argument of a type-parameter (14.1), or
the type-id of a template-argument for a type-parameter (14.3.1).
[ Example:
typedef int FIC(int) const;
FIC f; // ill-formed: does not declare a member function
struct S {
FIC f; // OK
};
FIC S::*pm = &S::f; // OK
—end example ]
In short, it's basically "half a type" that you can use to declare a class member function or a pointer-to-member type (or pass as a template parameter).
#include <functional>
template <typename T>
struct function_remove_const;
template <typename R, typename... Args>
struct function_remove_const<R(Args...)>
{
using type = R(Args...);
};
template <typename R, typename... Args>
struct function_remove_const<R(Args...)const>
{
using type = R(Args...);
};
int main()
{
std::function<function_remove_const<double(float)const>::type> f;
}
Live demo link.