Passing a concept-constrained function overload - c++

The following code fails to compile (Godbolt link):
#include <concepts>
template <class Fn>
decltype(auto) g(Fn&& fn) { return fn(); }
template <typename T>
requires(std::integral<T>) int f() { return 0; }
template <typename T>
int f() { return 1; }
int main() {
f<int>();
f<void>();
g(f<int>); // error: invalid initialization of non-const reference of type 'int (&)()'
// from an rvalue of type '<unresolved overloaded function type>'
g(f<void>);
}
It seems unexpected to me that the overload resolution succeeds when calling f<int>() (selecting the constrained version as a better match than the unconstrained version) but fails when passing f<int> as an argument.
Note that changing the unconstrained version to a disjoint constraint does make it compile (Godbolt link):
#include <concepts>
template <class Fn>
decltype(auto) g(Fn&& fn) { return fn(); }
template <typename T>
requires(std::integral<T>) int f() { return 0; }
template <typename T>
requires(!std::integral<T>) int f() { return 1; }
int main() {
f<int>();
f<void>();
g(f<int>);
g(f<void>);
}
So is the compiler behavior correct? And if so, is this an inconsistency in the standard, or is it intended to work this way?

It seems that neither GCC nor Clang has fully implemented the rules for forming pointers to constrained functions: [over.over]/5 definitely considers constraint ordering in choosing an overload. There were some late changes to these, although they’re just as relevant to the disjoint-constraints case as to the unconstrained case.

Related

Is it possible to explicitly instantiate a templated function that returns an unspecified type?

I'm trying to perform explicit instantiation of templated functions to improve accuracy of my code coverage results. I know how to do this when the return type is known. I can indirectly achieve the desired outcome through a definition of a wrapper function that returns void. Is it possible to achieve the outcome without this additional function definition?
#include <concepts>
template <typename T>
struct Gen {
T operator()() const { return T{}; }
};
template <std::default_initializable T>
Gen<T> buildKnown() {
return Gen<T>{};
}
template <std::default_initializable T>
std::invocable auto buildConstrained() {
return Gen<T>{};
}
template <std::default_initializable T>
std::invocable auto buildInternal() /* -> decltype(??) */ {
struct Local : Gen<T> {};
return Local{};
}
// Perform explicit instantiation of each templated function.
template Gen<int> buildKnown<int>(); // works
template Gen<int> buildConstrained<int>(); // somewhat surprised this doesn't work.
template auto buildInternal<int>(); // not surprised this doesn't work
// Indirectly instantiate buildInternal through a wrapper function.
template <typename T> void buildInternalWrapper() { buildInternal<T>(); }
template void buildInternalWrapper<int>();
EDIT: Fixed type constraint (was originally std::invocable<int>) that makes the question more confusing.
Well, first of all, your deduced return type constraints are wrong. Consider for example this function template that you wrote:
template <std::default_initializable T>
std::invocable<T> auto buildConstrained() {
return Gen<T>{};
}
What will happen if this is instantiated with T = int? It will become
std::invocable<int> auto buildConstrained() {
return Gen<int>{};
}
The placeholder type specifier std::invocable<int> auto means that the type needs to be deduced, and, if we call this deduced type U, then the constraint std::invocable<U, int> must be satisfied. So this function definition is only well-formed if std::invocable<Gen<int>, int> is satisfied. But it's not; Gen<int> is invocable with 0 arguments, not with 1 int argument.
I'm guessing you meant:
template <std::default_initializable T>
std::invocable auto buildConstrained() {
return Gen<T>{};
}
Having made such an amendment, we can now declare the explicit instantiation:
template std::invocable auto buildConstrained<int>();
An explicit instantiation of a function template that uses a placeholder type specifier must also use the same placeholder type specifier, not the deduced type.

Why doesn't SFINAE select the const-reference-taking overload? [duplicate]

This question already has an answer here:
Does SFINAE apply to function bodies?
(1 answer)
Closed 4 years ago.
In this code
struct A {int commmon; int rare;};
struct B {int commmon;};
struct L {
template<class T>
int f(const T& t) {return t.commmon;}
template<class T>
int f(T& t) {return t.rare;}
};
void func() {
A a; B b; L l;
l.f(a);
l.f(B{});
l.f(b);
}
the final lines gives me the error
In instantiation of ‘int L::f(T&) [with T = B]’:
error: ‘struct B’ has no member named ‘rare’
But according to my understanding of SFINAE, the second overload should be ignored because of the substitution failure in the body. Why doesn't this happen?
EDIT: If I change the return type of the second overload to decltype(T::rare), it does what I want. So where does my SF need to happen to be NAE?
The least verbose way to fix that is to use an auto return type with a trailing return type on the more constraint overload:
struct L {
template <class T>
auto f(const T& t) {return t.commmon;}
template <class T>
auto f(T& t) -> decltype(t.rare) {return t.rare;}
};
The advantage of this approach is that the constraint is specified at a point where the compiler has already seen the function argument, allowing for a shorter notation than std::enable_if clauses in the declaration of template parameters:
#include <type_traits>
struct L {
template <class T>
int f(const T& t) {return t.commmon;}
template <class T, std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, int> = 0>
int f(T& t) { return t.rare;}
};
Note further that the more constrained function won't be called when passing an rvalue argument. You might want to fix that by changing the function signatures to
template<class T /*, ... */>
int f(T&& t) { /* ... */ }
SFINAE does not apply to function bodies [temp.deduct/8]:
Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.
Template arguments are deduced before the function actually is instantiated. At the time the implementation is evaluated, the deduction has already occurred. So all that's relevant for deduction is the function signature, and from the existing ones, the non-const variant is fine to select.
If you want to apply get SFINAE applied depending on members existing, you have to do this within the function signature already. lubgr's answer reflects this nicely: The return type is decltype(t.rare); if T does not provide a rare member, the return type cannot be deduced and thus the overload is not considered during resolution.
Found two other answers dealing with the matter you might be interested in: C++11, compatible to pre-C++11

Default lambda as templated parameter of a function

Consider the following code
template<bool b, typename T> void foo(const T& t = []() {}) {
// implementation here
}
void bar() {
foo<true>([&](){ /* implementation here */ }); // this compiles
foo<true>(); // this doesn't compile
}
In the case that doesn't compile I get the following errors:
error C2672: 'foo': no matching overloaded function found
error C2783: 'void foo(const T&)': could not deduce template argument for 'T'
I think it's clear what I want to achieve: let foo be called with and without a client-provided lambda. The compiler is MSVC++2017 version 15.4.4 toolset v141.
Default function arguments are not part of the template argument deduction process. To quote [temp.deduct.partial]/3:
The types used to determine the ordering depend on the context in
which the partial ordering is done:
In the context of a function call, the types used are those function parameter types for which the function call has arguments.
141
141) Default arguments are not considered to be arguments in this
context; they only become arguments after a function has been
selected.
That bullet and note indicate that since you didn't provide an argument for t in the call to foo, the type T cannot be deduced. The default lambda argument can only be taken into account if the function is selected to be called, not before.
The solution, as all the others have noted, is to provide an overload without parameters, that will call the templated one with the default lambda you have in mind.
Another (very efficient) way - default T to be a null functor.
// no_op is a function object which does nothing, regardless of how many
// arguments you give it. It will be elided completely unless you compile with
// -O0
struct no_op
{
template<class...Args>
constexpr void operator()(Args&&...) const {}
};
// foo defaults to using a default-constructed no_op as its function object
template<bool b, typename T = no_op> void foo(T&& t = T())
{
// implementation here
t();
}
void bar() {
foo<true>([&](){ std::cout << "something\n"; }); // this compiles
foo<true>(); // this now compiles
}
The compiler uses the arguments passed to deduce the template type. If there's no arguments, then how would the compiler be able to deduce the template type?
You can use overloading instead of default arguments here.
The overloaded non-argument function can simply call the function with the "default" argument:
template<bool b, typename T> void foo(const T& t) {
// implementation here
}
template<bool b> void foo() {
foo<b>([]() {});
}
Consider overloading it directly:
template <bool b>
void foo(void) {
foo([](){});
}
See CppReference:
Non-deduced contexts
4) A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done:
Type template parameter cannot be deduced from the type of a function default argument:
template void f(T = 5, T = 7);
void g()
{
f(1); // OK: calls f<int>(1, 7)
f(); // error: cannot deduce T
f<int>(); // OK: calls f<int>(5, 7)
}
You are trying to say something that makes no sense. You are asking the compiler to guess T from your arguments, but then you do not provide any argument.
The following code does compile, and does what you want:
template<bool b, typename T> void foo(const T& t) {
// implementation here
}
template<bool b> void foo() {
foo<b>([]() {}); // Call actual implementation with empty lambda
}
void bar() {
foo<true>([&](){ /* implementation here */ }); // this compiles
foo<true>(); // this now compiles as well
}

Why SFINAE doesn't work in right side in default function arguments?

I have this code:
struct My
{
typedef int foo;
};
struct My2
{
};
template <typename T>
void Bar(const T&, int z = typename T::foo())
{
std::cout << "My" << std::endl;
}
void Bar(...)
{
std::cout << "..." << std::endl;
}
int main()
{
My my;
Bar(my); // OK
My2 my2;
Bar(my2); // Compile error: no type named ‘foo’ in ‘struct My2’
return 0;
}
I suppose, that if some class T doesn't have typedef foo inside, compiler should exclude first overload and choose overload with ellipsis. But I check this code on MSVC, gcc and clang and I get compile error on those compilers. Why SFINAE doesn't work in this case?
The type of z is not subject to template substitution, it is always int. This means there is no opportunity for SFINAE, and you instead get a compiler error when attempting to resolve T::foo for the default value. Default arguments do not participate in overload resolution, instead being instantiated only when missing from the function call. Section 14.7.1 (paragraphs 13/14) of the standard describes this behaviour, but does not give justification for the lack of SFINAE here.
SFINAE can be allowed to happen by making the type of z a template parameter, as below:
(live example: http://ideone.com/JynMye)
#include <iostream>
struct My
{
typedef int foo;
};
struct My2
{
};
template<typename T, typename I=typename T::foo> void Bar(const T&, I z = I())
{
std::cout << "My\n";
}
void Bar(...)
{
std::cout << "...\n";
}
int main()
{
My my;
Bar(my); // OK
My2 my2;
Bar(my2); // Also OK
return 0;
}
This will use the "My" version for the first call, and the "..." version for the second call. The output is
My
...
However, if void Bar(...) was a template, for whatever reason, the "My" version will never get a chance:
(live example: http://ideone.com/xBQiIh)
#include <iostream>
struct My
{
typedef int foo;
};
struct My2
{
};
template<typename T, typename I=typename T::foo> void Bar(const T&, I z = I())
{
std::cout << "My\n";
}
template<typename T> void Bar(T&)
{
std::cout << "...\n";
}
int main()
{
My my;
Bar(my); // OK
My2 my2;
Bar(my2); // Also OK
return 0;
}
Here, the "..." version is called in both cases. The output is:
...
...
One solution is to use class template (partial) specialisation; provide the "..." version as the base, with the type of the second parameter defaulted to int, and the "My" version as a specialisation where the second parameter is typename T::foo. In conjunction with a plain template function to deduce T and dispatch to the appropriate class' member function, this produces the desired effect:
(live example: http://ideone.com/FanLPc)
#include <iostream>
struct My
{
typedef int foo;
};
struct My2
{
};
template<typename T, typename I=int> struct call_traits {
static void Bar(...)
{
std::cout << "...\n";
}
};
template<typename T> struct call_traits<T, typename T::foo> {
static void Bar(const T&, int z=typename T::foo())
{
std::cout << "My\n";
}
};
template<typename T> void Bar(const T& t)
{
call_traits<T>::Bar(t);
}
int main()
{
My my;
Bar(my); // OK
My2 my2;
Bar(my2); // Still OK
return 0;
}
Here, the output is:
My
...
The type z is an int, is not being deduced by the compiler, no room for SFINAE to take place. The value being used to initialise z is based on the default of T::foo, which doesn't exist; hence the error.
If the type for z is elevated to the template itself, substitution can now fail, and SFINAE kicks in.
#include <iostream>
struct My
{
typedef int foo;
};
struct My2
{
};
template <typename T, typename I = typename T::foo>
void Bar(const T&, I z = I())
{
(void)z; // silence any warnings on unused
std::cout << "My" << std::endl;
}
void Bar(...)
{
std::cout << "..." << std::endl;
}
int main()
{
My my;
Bar(my);
My2 my2;
Bar(my2); // Compiles
return 0;
}
Live sample
In order for a function template to be part of the overloaded list of candidate functions, the template argument deduction must succeed. If it fails, then the candidate is removed from the list. Hence, if no deduction failure occurs, it is added to the candidate list (but this does not preclude further errors if it is finally selected).
14.8.3/1 Overload resolution
A function template can be overloaded either by (non-template) functions of its name or by (other) function templates of the same name. When a call to that name is written (explicitly, or implicitly using the operator notation), template argument deduction (14.8.2) and checking of any explicit template arguments (14.3) are performed for each function template to find the template argument values (if any) that can be used with that function template to instantiate a function template specialization that can be invoked with the call arguments. For each function template, if the argument deduction and checking succeeds, the template arguments (deduced and/or explicit) are used to synthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, for a given function template, argument deduction fails, no such function is added to the set of candidate functions for that template. The complete set of candidate functions includes all the synthesized declarations and all of the non-template overloaded functions of the same name. The synthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in 13.3.3.
Template argument deduction is performed on the function type and its template arguments themselves.
14.8.2/8 Template argument deduction
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. —end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.
From the OP, the function Bar<T> is added to the candidate list since it can be deduced what the type for T is. It is instantiated and the default arguments are checked, and hence it fails.
14.7.1/13 Implicit instantiation
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.
Quotes taken from draft n3797
One more C++03 compatible option for you. Because in answers above default argument was used in template function and it is not permitted in standard.
#include <iostream>
struct TypeWithFoo{
typedef int Foo;
};
template<typename T, bool>
struct onFooAction;
template<typename T>
struct onFooAction<T, false>{
void operator ()(const T &t){
std::cout << "No foo :(\n";
}
};
template<typename T>
struct onFooAction<T, true>{
void operator ()(const T &t){
std::cout << "Foo =)\n";
}
};
template<typename T>
struct hasFoo{
typedef char yes[1];
typedef char no[2];
template<typename C>
static yes& testForFoo(typename C::Foo*);
template<typename>
static no& testForFoo(...);
static const bool value = sizeof(testForFoo<T>(0)) == sizeof(yes);
};
template<typename T>
void bar(const T &t){
onFooAction<T, hasFoo<T>::value>()(t);
}
int main(){
bar(10);
bar(TypeWithFoo());
}

How are template definitions matched to template declarations?

How exactly is a template declaration matched to a template definition? I found some text in the standard about template-ids referring to the same function if "their template-names [...] refer to the same template and [...]" (14.4 [temp.type] p1) but I can't find a definition for template-names or when template-names refer to the same template. I'm not sure if I'm on the right track anyway because I haven't deciphered the grammar well enough to tell if a template-id is part of the definition/declaration of a template, or just a use of a template.
For example, the following program works fine.
#include <iostream>
template<typename T>
T foo(T t);
int main() {
foo(1);
}
template<typename T>
T foo(T t)
{ std::cout << "A\n"; return 0; }
If I change the way I use the template parameters in the template definition the names apparently no longer refer to the same template, and linking fails.
#include <iostream>
template<typename T>
T foo(T t);
int main() {
foo(1);
}
template<typename T>
int foo(T t) { std::cout << "A\n"; return 0; }
// or
template<typename T>
struct identity {
typedef T type;
};
template<typename T>
typename identity<T>::type
foo(T t) { std::cout << "A\n"; return 0; }
Next, if I move the template definition to another translation unit, for my implementation of C++ (MSVC 11 beta) the program works no matter how I say the types.
//main.cpp
template<typename T>
T foo(T t);
int main() {
foo(1);
}
//definition.cpp
#include <iostream>
template<typename T>
struct identity {
typedef T type;
};
template<typename T>
typename identity<T>::type
foo(T t) { std::cout << "A\n"; return 0; }
template int foo<int>(int);
or
//definition.cpp
#include <iostream>
template<typename T>
int foo(T t) { std::cout << "A\n"; return 0; }
template int foo<int>(int);
or even if the definition isn't a template at all:
//definition.cpp
#include <iostream>
int foo(T t) { std::cout << "A\n"; return 0; }
Obviously linking is succeeding because the signature/mangled name is the same regardless of the template that was instantiated to create the symbol. I think this undefined behavior because I'm violating:
§ 14.1 [temp] p6
A function template, member function of a class template, or static
data member of a class template shall be defined in every translation
unit in which it is implicitly instantiated (14.7.1) unless the
corresponding specialization is explicitly instantiated (14.7.2) in
some translation unit; no diagnostic is required.
But then say I try to fulfill those requirements by putting a definition of the template in the second translation unit, and including an explicit instantiation at one of two locations:
#include <iostream>
template<typename T>
T foo(T t) { std::cout << "A\n"; return 0; }
// Location 1
template<typename T>
int foo(int t) { std::cout << "B\n"; return 0; }
// Location 2
What are the rules about disambiguating what template an explicit instantiation refers to? Putting it at Location 1 causes the correct template to be instantiated and that definition to be used in the final program, while putting it at Location 2 instantiates the other template, and causes what I believe is undefined behavior under 14.1 p6 above.
On the other hand an implicit instantiation of two templates definitions picks the first template no matter what, so it seems like the rule for disambiguating the templates is different in these circumstances:
#include <iostream>
template<typename T>
T foo(T t) { std::cout << "A\n"; return 0; }
template<typename T>
int foo(int t) { std::cout << "B\n"; return 0; }
int main() {
foo(1); // prints "A"
}
The reason this came up is related to this question where the questioner discovered that a single forward declaration
template<typename T>
T CastScriptVarConst(const ScriptVar_t& s);
could not act as a declaration of multiple template definitions:
template<typename T>
typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
return (T) s;
}
template<typename T>
typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ)
&& std::is_base_of<CustomVar,T>::value,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
return *s.as<T>();
}
And I wanted to better understand the relationship between template definitions and declarations.
OK, let's start from the beginning. The "template-name" of a template is the actual name of the function or class being templated; that is, in
template<class T> T foo(T t);
foo is the template name. For function templates, the rule for deciding whether they are the same is quite long, described in 14.5.5.1 "Function template overloading". Paragraph 6 of that section (I'm quoting from C++03 here, so the wording and paragraph numbers might have changed in C++11) defines the terms equivalent and functionally equivalent, when applied to expressions involving template parameters.
In short, equivalent expressions are the same apart from possibly having different names for template parameters, and functionally equivalent expressions are the same if they happen to evaluate to the same thing. For example, the first two f declarations are equivalent, but the third is only functionally equivalent to the other two:-
template<int A, int B>
void f(array<A + B>);
template<int T1, int T2>
void f(array<T1 + T2>);
template<int A, int B>
void f(array< mpl::plus< mpl::int<A>, mpl::int<B> >::value >);
It goes on in paragraph 7 to extend those two definitions to whole function templates. Two function templates that match (in name, scope, and template parameter lists) are equivalent if they also have equivalent return types and argument types, or functionally equivalent if they only have functionally equivalent return types and argument types. Looking at your second example, these two functions are only functionally equivalent:-
template<typename T>
T foo(T t);
template<typename T>
typename identity<T>::type foo(T t);
Paragraph 7 closes with the dire warning that, "If a program contains declarations of function templates that are functionally equivalent but not equivalent, the program is ill-formed; no diagnostic is required." Your second example is, thus, not valid C++. Detecting errors like that would require each declaration and definition of a function template to be annotated in the binary with an AST describing the template expression each parameter and the return type came from, which is why the standard doesn't require implementations to detect it. MSVC is justified in compiling your third example how you intended, but it would be just as justified to break.
Moving on to explicit instantiation, the important section is 14.7, "Template instantiation and specialization". Paragraph 5 disallows all of the following:
Explicitly instantiating a template more than once;
Explicitly instantiating and explicitly specializing the same template;
Explicitly specializing a template for the same set of arguments more than once.
Again, "no diagnostic is required" as it's quite hard to detect.
So to expand your explicit instantiation example, the following code breaks the second rule and is illegal :-
/* Template definition. */
template<typename T>
T foo(T t)
{ ... }
/* Specialization, OK in itself. */
template< >
int foo(int t)
{ ... }
/* Explicit instantiation, OK in itself. */
template< >
int foo(int t);
This is illegal regardless of the locations of the explicit specialization and the explicit instantiation, but of course because no diagnostic is required, you may get useful results on some compilers. Note also the difference between explicit instantiation and explicit specialization. The following example is ill-formed because it declares an explicit specialization without defining it:-
template<typename T>
T f(T f)
{ ... }
template< >
int f(int);
void g(void)
{ f(3); }
but this example is well-formed, because it has an explicit instantiation:-
template<typename T>
T f(T f)
{ ... }
template f(int);
void g(void)
{ f(3); }
The < > makes all the difference. Be warned also that even when you do define an explicit specialization, it has to be before you use it, otherwise the compiler might already have generated an implicit instantiation for that template. This is in 14.7.3 "Explicit specialization" paragraph 6, just below where you were reading, and again, no diagnostic is required. To adapt the same example, this is ill-formed:-
template<typename T>
T f(T f)
{ ... }
void g(void)
{ f(3); } // Implicitly specializes int f(int)
template< >
int f(int) // Too late for an explicit specialization
{ ... }
If you weren't confused enough yet, take a look at your last example:-
template<typename T>
T foo(T t) { ... }
template<typename T>
int foo(int t) { ... }
The second definition of foo is not a specialization of the first definition. It would have to be template< > int foo(int) to be a specialization of template<typename T> T foo(T). But that's OK: function overloading is allowed, and it's allowed between function templates and normal functions. Calls of the form foo(3) will always use the first definition, because its template parameter T can be deduced from the argument type. The second definition does not allow its template parameter to be deduced from the argument type. Only by explicitly specifying T can you reach the second definition, and only then when the call is not ambiguous with the first definition:-
f<int>(3); // ambiguous
f<string>(3); // can only be the second one
The whole process of doing overload resolution for function templates is too long to describe here. Read section 14.8.3 if you're interested and ask more questions :-)