How can I make the following code compile?
I'm trying to check if BigStruct exist in a type, and enable f if it does.
#include <type_traits>
struct A {
using BigStruct = int;
};
struct C {
};
template <typename T>
struct B {
void f(typename T::BigStruct t) requires requires {T::BigStruct;} {}
};
int main() {
B<A> b1;
B<C> b2;
}
Error I got:
<source>:11:24: error: no type named 'BigStruct' in 'C'
void f(typename T::BigStruct t) requires requires {T::BigStruct;} {}
~~~~~~~~~~~~^~~~~~~~~
<source>:16:8: note: in instantiation of template class 'B<C>' requested here
B<C> b2;
^
1 error generated.
ASM generation compiler returned: 1
<source>:11:24: error: no type named 'BigStruct' in 'C'
void f(typename T::BigStruct t) requires requires {T::BigStruct;} {}
~~~~~~~~~~~~^~~~~~~~~
<source>:16:8: note: in instantiation of template class 'B<C>' requested here
B<C> b2;
^
1 error generated.
Execution build compiler returned: 1
Here's a godbolt link for x86-64 clang trunk.
Concept checking for non-template functions happens after the function's signature has been generated. That means the parameter list has to exist. And therefore, it must be syntactically valid.
There's not much you can do in this circumstance, if you don't want to restrict the entire class, besides the old pre-C++20 strategy of making the function itself a template:
template<typename U = T>
requires requires {typename U::BigStruct;}
void f(typename U::BigStruct t) {}
Related
g++ happily accepts the following code, whereas clang nor msvc are able to match out of line definitions.
Any idea why ?
template <bool B>
struct test
{
test() requires (B);
test() requires(!B);
};
template <>
test<true>::test()
{}
template <>
test<false>::test()
{}
int main()
{
test<false> a;
test<true> b;
return 0;
}
Demo
Clang:
error: out-of-line definition of 'test' does not match any declaration in 'test<true>'
Msvc:
error C2244: 'test<true>::test': unable to match function definition to an existing declaration
You're declaring constrained constructors but defining two unconstrained specializations. Those won't ever match.
What you probably meant was:
template <bool B>
struct test
{
test() requires (B);
test() requires(!B);
};
template <bool B>
test<B>::test() requires (B)
{}
template <bool B>
test<B>::test() requires (!B)
{}
This compiles fine in all 3 compilers.
As to why your original version compiles - it's a GCC bug 96830. Clang is right, code is ill-formed because the out-of-line definitions don't match the template definition (note also that template<> ... is full specialization syntax).
See [temp.class.general]/3 (emphasis mine):
When a member of a class template is defined outside of the class template definition, the member definition is defined as a template definition with the template-head equivalent to that of the class template.
[temp.over.link]/6:
Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent and are both declared with type-constraints that are equivalent if either template-parameter is declared with a type-constraint, and if either template-head has a requires-clause, they both have requires-clauses and the corresponding constraint-expressions are equivalent.
Also see [temp.mem.func]/1 for an example of declaring a constrained member out-of-line:
template<typename T> struct S {
void f() requires C<T>;
void g() requires C<T>;
};
template<typename T>
void S<T>::f() requires C<T> { } // OK
template<typename T>
void S<T>::g() { } // error: no matching function in S<T>
This question already has an answer here:
a constant in dependent base class make out-of-line definition not matched?
(1 answer)
Closed 2 years ago.
This is a follow up of Template class type alias failing substitution in member declaration
Consider this code:
// A
template <typename T>
struct foo {
using type = unsigned;
template <type x>
void bar(type (&)[x]);
};
template <typename T>
template <typename foo<T>::type x>
void foo<T>::bar(type (&)[x]){}
gcc emits the following error:
<source>:13:6: error: no declaration matches 'void foo<T>::bar(foo<T>::type (&)[x])'
13 | void foo<T>::bar(type (&)[x]){}
| ^~~~~~
<source>:8:10: note: candidate is: 'template<class T> template<unsigned int x> void foo<T>::bar(foo<T>::type (&)[x])'
8 | void bar(type (&)[x]);
| ^~~
<source>:4:8: note: 'struct foo<T>' defined here
4 | struct foo {
| ^~~
Compiler returned: 1
clang:
<source>:13:14: error: out-of-line definition of 'bar' does not match any declaration in 'foo<T>'
void foo<T>::bar(type (&)[x]){}
^~~
1 error generated.
Compiler returned: 1
When I remove what is identical in the erroneous definition and the candidate I get this:
// B
template <typename T>
struct foo {
using type = unsigned;
template <type x>
void bar();
};
template <typename T>
template <typename foo<T>::type x>
void foo<T>::bar(){}
This compiles fine (gcc / clang)
An attempt to answer the original question (by Darhuuk, slightly modified) was this:
// C
#include <type_traits>
template <typename T> struct length { using type = unsigned int; };
template <typename T> using length_t = typename length<T>::type;
template <typename type>
class Object {
template <length_t<Object<type>> length>
void put(type (&)[length]);
};
template <typename type>
template <length_t<Object<type>> length>
void Object<type>::put(type (&)[length]) {}
int main() {}
Clang seems to have similar problems as with the original code and emits the error:
<source>:15:20: error: out-of-line definition of 'put' does not match any declaration in 'Object<type>'
void Object<type>::put(type (&)[length]) {}
^~~
1 error generated.
while gcc compiles it without complaints.
Who is right about C? Is it a bug in clang or is gcc being lax?
Why does A not compile while B does?
as I mentioned in
Template class type alias failing substitution in member declaration:
CWG2, the ancient issue that nobody knows when it's posted, is still drafting, which means the match rule of out-of-definition is even unspecified. these weird mismatches are because of the different implementations of compilers.
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.
I have this function template:
template <class TemplateArgument, template<class> class TemplateType>
TemplateArgument f(const TemplateType<TemplateArgument>& arg)
{
return TemplateArgument();
}
When used like this, it fails to compile:
struct A {};
template <typename T> struct S {};
template <typename T> struct B : public S<T> {};
struct C : public B<A> {};
int main()
{
f(C());
return 0;
}
And the error message is:
<source>: In function 'int main()':
<source>:15:10: error: no matching function for call to 'f(C)'
f(C());
^
<source>:2:18: note: candidate: template<class TemplateArgument, template<class> class TemplateType> TemplateArgument f(const TemplateType<TemplateArgument>&)
TemplateArgument f(const TemplateType<TemplateArgument>& arg)
^
<source>:2:18: note: template argument deduction/substitution failed:
<source>:15:10: note: 'const TemplateType<TemplateArgument>' is an ambiguous base class of 'C'
f(C());
^
Happens with GCC (any version) and clang (any version). Does not happen with MSVC. Live demo: https://godbolt.org/g/eWxeHJ
Why does this error occur? I fail to see any ambiguity, the "ambiguous base class" error usually occurs in multiple inheritance situations, does it not?
How can I make my code compile (deduce template arguments correctly)?
Note that I cannot edit the A, B, C, S classes and their relation to each other, I can only edit my function f() to accept these classes properly.
The compiler is not sure whether to deduce args type as B<A> or S<A>. I'm not sure about this specific case but MSVC is known to violate the standard especially when it comes to templates.
As for your function, you need to resolve this ambiguity yourself by explicitly casting to the appropriate base:
f((const B<A> &)C());
or by specifying template parameters explicitly:
f<A, B>(C());
Generally whenever there is any ambiguity in the language it is never automatically resolved by the compiler because it would just be a speculation about what exactly did user intend, which might be right in some cases and completely wrong in others.
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.