Overload resolution between template members in base and derived classes - c++

Microsoft compiler (Visual Studio 2017 15.2) rejects the following code:
#include <type_traits>
struct B
{
template<int n, std::enable_if_t<n == 0, int> = 0>
void f() { }
};
struct D : B
{
using B::f;
template<int n, std::enable_if_t<n == 1, int> = 0>
void f() { }
};
int main()
{
D d;
d.f<0>();
d.f<1>();
}
The error is:
error C2672: 'D::f': no matching overloaded function found
error C2783: 'void D::f(void)': could not deduce template argument for '__formal'
note: see declaration of 'D::f'
Clang also rejects it:
error: no matching member function for call to 'f'
d.f<0>();
~~^~~~
note: candidate template ignored: disabled by 'enable_if' [with n = 0]
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
GCC perfectly accepts it. Which compiler is right?
Addition:
With SFINAE in the form
template<int n, typename = std::enable_if_t<n == 0>>
...
template<int n, typename = std::enable_if_t<n == 1>>
GCC also produces an error:
error: no matching function for call to ‘D::f<0>()’
d.f<0>();
^
note: candidate: template<int n, class> void D::f()
void f()
^
note: template argument deduction/substitution failed:

Turning cppleaner's comment into an answer:
From namespace.udecl#15.sentence-1:
When a using-declarator brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list, cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting)
Unfortunately, template parameter doesn't count and both f has empty parameter-type-list, are not const and no ref-qualifier.
Derived::f so hides Base::f.
gcc is wrong to accept that code.
So the way to fix it is by default argument (returned type doesn't count either):
struct B
{
template <int n>
void f(std::enable_if_t<n == 0>* = nullptr) { }
};
struct D : B
{
using B::f;
template <int n>
void f(std::enable_if_t<n == 1>* = nullptr) { }
};

Related

Deduce template parameter of class member from constructor of class

I have a class A which contains a templated member B whose exact type should be deduced from A's constructor. The way this is supposed to work is that, as shown in the below example, B can be instantiated with either 1 or 2 parameters to its constructor (deduction guide will tell) but will take a const char* in any case. When I instantiate A with the const char* argument for the constructor, an object B should be instantiated from the const char* as A only takes a B object. However, this is how far I get:
#include <iostream>
template <bool LengthOpt>
struct B
{
B(const char*) { }
B(const char*, size_t) { }
void print() {
if constexpr (LengthOpt) {
std::cout << "LengthOpt is set" << std::endl;
}
}
};
B(const char*) -> B<false>;
B(const char*, size_t) -> B<true>;
template <template <bool LengthOpt> class T>
struct A
{
A(T is) : is_{is} {
}
void print() {
is_.print();
}
T is_;
};
int main()
{
A a("hello");
a.print();
}
And it yields those errors:
<source>:24:7: error: use of template template parameter 'T' requires template arguments; argument deduction not allowed in function prototype
A(T is) : is_{is} {
^
<source>:21:43: note: template is declared here
template <template <bool LengthOpt> class T>
^
<source>:32:5: error: use of template template parameter 'T' requires template arguments; argument deduction not allowed in non-static struct member
T is_;
^
<source>:21:43: note: template is declared here
template <template <bool LengthOpt> class T>
^
<source>:37:7: error: no viable constructor or deduction guide for deduction of template arguments of 'A'
A a("hello");
^
<source>:22:8: note: candidate template ignored: could not match 'A<T>' against 'const char *'
struct A
^
<source>:22:8: note: candidate function template not viable: requires 0 arguments, but 1 was provided
My take on the problem is that the compiler doesn't know that I want to instantiate an object B in A's constructor, as the template template argument specifies nothing. It could very well be just any object that takes one template parameter.
I'm scratching my head right now on how to resolve this. Is it even possible or am I scratching a limitation in C++ again?

Using using-declarations to overload with base class, gcc accepts clang rejects [duplicate]

Microsoft compiler (Visual Studio 2017 15.2) rejects the following code:
#include <type_traits>
struct B
{
template<int n, std::enable_if_t<n == 0, int> = 0>
void f() { }
};
struct D : B
{
using B::f;
template<int n, std::enable_if_t<n == 1, int> = 0>
void f() { }
};
int main()
{
D d;
d.f<0>();
d.f<1>();
}
The error is:
error C2672: 'D::f': no matching overloaded function found
error C2783: 'void D::f(void)': could not deduce template argument for '__formal'
note: see declaration of 'D::f'
Clang also rejects it:
error: no matching member function for call to 'f'
d.f<0>();
~~^~~~
note: candidate template ignored: disabled by 'enable_if' [with n = 0]
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
GCC perfectly accepts it. Which compiler is right?
Addition:
With SFINAE in the form
template<int n, typename = std::enable_if_t<n == 0>>
...
template<int n, typename = std::enable_if_t<n == 1>>
GCC also produces an error:
error: no matching function for call to ‘D::f<0>()’
d.f<0>();
^
note: candidate: template<int n, class> void D::f()
void f()
^
note: template argument deduction/substitution failed:
Turning cppleaner's comment into an answer:
From namespace.udecl#15.sentence-1:
When a using-declarator brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list, cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting)
Unfortunately, template parameter doesn't count and both f has empty parameter-type-list, are not const and no ref-qualifier.
Derived::f so hides Base::f.
gcc is wrong to accept that code.
So the way to fix it is by default argument (returned type doesn't count either):
struct B
{
template <int n>
void f(std::enable_if_t<n == 0>* = nullptr) { }
};
struct D : B
{
using B::f;
template <int n>
void f(std::enable_if_t<n == 1>* = nullptr) { }
};

C++ Template overload with enable_if: different behaviour with g++ and clang

During resolution of an overload of a templated member function of a base class, I observed a different behaviour between g++ (5.2.1-23) and clang (3.8.0), with -std=c++14.
#include <iostream>
#include <type_traits>
struct Base
{
template <typename T>
auto a(T t) -> void {
std::cout<< "False\n";
}
};
template <bool Bool>
struct Derived : public Base
{
using Base::a;
template <typename T, bool B = Bool>
auto a(T t) -> std::enable_if_t<B, void>
{
std::cout<< "True\n";
}
};
int main()
{
Derived<true> d;
d.a(1); // fails with g++, prints "true" with clang
Derived<false> d2;
d2.a(1); // fails with clang++, prints "false" with g++
}
The call to Derived<true>::a fails with g++ with the following message:
test.cc: In function ‘int main()’:
test.cc:28:8: error: call of overloaded ‘a(int)’ is ambiguous
d.a(1);
^
test.cc:18:8: note: candidate: std::enable_if_t<B, void> Derived<Bool>::a(T) [with T = int; bool B = true; bool Bool = true; std::enable_if_t<B, void> = void]
auto a(T t) -> std::enable_if_t<B, void>
^
test.cc:7:8: note: candidate: void Base::a(T) [with T = int]
auto a(T t) -> void {
^
and the call to Derived<false>::a fails with clang++ with the following message:
test.cc:32:6: error: no matching member function for call to 'a'
d2.a(1);
~~~^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with T = int, B = false]
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
^
My guess is that they interpret differently the using Base::a;, and that it isn't considered in clang, whereas it's (maybe too much) considered in g++. What I thought would happen is that if Derived has true as parameter, then the call of a() is dispatched to Derived's implementation, whereas if the parameter is false, the call is dispatched to Base::a.
Are they both wrong? Who is right? Who should I submit a bug report to? Can somebody explain what is going on?
Thanks
From 3.3.10/p3 Name hiding [basic.scope.hiding]:
In a member function definition, the declaration of a name at block
scope hides the declaration of a member of the class with the same
name; see 3.3.7. The declaration of a member in a derived class
(Clause 10) hides the declaration of a member of a base class of the
same name; see 10.2
Also 7.3.3/p15 The using declaration [namespace.udecl]:
When a using-declaration brings names from a base class into a derived
class scope, member functions and member function templates in the
derived class override and/or hide member functions and member
function templates with the same name, parameter-type-list (8.3.5),
cv-qualification, and ref-qualifier (if any) in a base class (rather
than conflicting). [ Note: For using-declarations that name a
constructor, see 12.9. — end note ] [Example:
struct B {
virtual void f(int);
virtual void f(char);
void g(int);
void h(int);
};
struct D : B {
using B::f;
void f(int); // OK: D::f(int) overrides B::f(int);
using B::g;
void g(char); // OK
using B::h;
void h(int); // OK: D::h(int) hides B::h(int)
};
void k(D* p)
{
p->f(1); // calls D::f(int)
p->f(’a’); // calls B::f(char)
p->g(1); // calls B::g(int)
p->g(’a’); // calls D::g(char)
}
— end example ]
This is resolved during member name look-up. Thus, it's before template argument deduction. Consequently, as correctly TC mentioned in the comments Base template function is hidden no matter of SFINAE verdict.
Therefore CLANG is correct and GCC is wrong.

Does SFINAE not apply here?

I was writing something to use SFINAE to not generate a function under certain conditions. When I use the meta code directly it works as expected, but when I use the code indirectly through another class, it fails to work as expected.
I thought that this was a VC++ thing, but looks like g++ also has this, so I'm wondering if there's some reason that SFINAE isn't being applied to this case.
The code is simple. If the class used isn't the base class of a "collection class", then don't generate the function.
#include <algorithm>
#include <type_traits>
#define USE_DIRECT 0
#define ENABLE 1
class A{};
class B{};
class C{};
class D{};
class collection1 : A, B, C {};
class collection2 : D {};
#if USE_DIRECT
template<typename X>
typename std::enable_if<std::is_base_of<X, collection1>::value, X>::type fn(X x)
{
return X();
}
# if ENABLE
template<typename X>
typename std::enable_if<std::is_base_of<X, collection2>::value, X>::type fn(X x)
{
return X();
}
# endif
#else // USE_DIRECT
template<typename X, typename COLLECTION>
struct enable_if_is_base_of
{
static const int value = std::is_base_of<X, COLLECTION>::value;
typedef typename std::enable_if<value, X>::type type;
};
template<typename X>
typename enable_if_is_base_of<X, collection1>::type fn(X x)
{
return X();
}
# if ENABLE
template<typename X>
typename enable_if_is_base_of<X, collection2>::type fn(X x)
{
return X();
}
# endif
#endif // USE_DIRECT
int main()
{
fn(A());
fn(B());
fn(C());
fn(D());
return 0;
}
If I set USE_DIRECT to 1 and ENABLE to 0, then it fails to compile as there is no function fn that takes a parameter D. Setting ENABLE to 1 will stop that error from occurring.
However, if I set USE_DIRECT to 0 and ENABLE to 0, it will fail with different error messages but for the same case, there is no fn that takes parameter D. Setting ENABLE to 1 however will cause a failure for all 4 function calls.
For your convience, here is the code in an online compiler: http://goo.gl/CQcXHr
Can someone explain what is happening here and why?
This looks like it may be related to Alias templates used in SFINAE lead to a hard error, but no one has answered that either.
For reference, here are the errors that were generated by g++:
main.cpp: In instantiation of 'struct enable_if_is_base_of<A, collection2>':
main.cpp:45:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = A]'
main.cpp:54:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, A>'
typedef typename std::enable_if<std::is_base_of<X, COLLECTION>::value, X>::type type;
^
main.cpp: In instantiation of 'struct enable_if_is_base_of<B, collection2>':
main.cpp:45:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = B]'
main.cpp:55:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, B>'
main.cpp: In instantiation of 'struct enable_if_is_base_of<C, collection2>':
main.cpp:45:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = C]'
main.cpp:56:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, C>'
main.cpp: In instantiation of 'struct enable_if_is_base_of<D, collection1>':
main.cpp:38:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection1>::type fn(X) [with X = D]'
main.cpp:57:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, D>'
Only a substitution that takes place in an immediate context may result in a deduction failure:
§14.8.2 [temp.deduct]/p8
Only invalid types and expressions in the immediate context of the function type and
its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types
and expressions can result in side effects such as the instantiation of class template specializations and/or
function template specializations, the generation of implicitly-defined functions, etc. Such side effects are
not in the “immediate context” and can result in the program being ill-formed. — end note ]
The signature of fn requires a full declaration of enable_if_is_base's specialization to exist:
§14.7.1 [temp.inst]/p1
Unless a class template specialization has been explicitly instantiated (14.7.2) or explicitly specialized (14.7.3),
the class template specialization is implicitly instantiated when the specialization is referenced in a context
that requires a completely-defined object type or when the completeness of the class type affects the semantics
of the program.
The compiler fails to generate the specialization of:
template<typename X, typename COLLECTION>
struct enable_if_is_base_of
{
static const int value = std::is_base_of<X, COLLECTION>::value;
typedef typename std::enable_if<value, X>::type type;
};
because the substitution of typename std::enable_if<value, X>::type results in a missing type if value evaluates to false, which is not in an immediate context.
As already answered your SFINAE scheme can't work. However, you could use the following not so pretty solution to achieve what you probably want:
#include <algorithm>
#include <type_traits>
#include <iostream>
class A{};
class B{};
class C{};
class D{};
class collection1 : A, B, C {};
class collection2 : D {};
template<typename X, class Enable = void>
struct enable_if_is_base_of;
template<typename X>
struct enable_if_is_base_of<X, typename std::enable_if<std::is_base_of<X, collection1>::value>::type> {
static X fn(X x) {
(void) x;
std::cout << "collection1" << std::endl;
return X();
}
};
template<typename X>
struct enable_if_is_base_of<X, typename std::enable_if<std::is_base_of<X, collection2>::value>::type> {
static X fn(X x) {
(void) x;
std::cout << "collection2" << std::endl;
return X();
}
};
int main() {
enable_if_is_base_of<A>::fn(A());
enable_if_is_base_of<B>::fn(B());
enable_if_is_base_of<C>::fn(C());
enable_if_is_base_of<D>::fn(D());
}
LIVE DEMO
The problem is that if the type doesn't exist, the typedef isn't skipped, it is an error.
Solution: no typedef
template<typename X, typename COLLECTION>
struct enable_if_is_base_of : std::enable_if<std::is_base_of<X, COLLECTION>::value, X>
{};
Now the type member exists (via inheritance) if and only if it exists within the enable_if instantiation.

Type specialization at compile-time

I'm writing a class which shares several different features with std::function (or at least the classes are in many ways similar). As you all know std::function is instantiated by specifying the template parameters (i.e std::function<void (std::string&)>), it is the same for my class. I have an exception though, I want to specialize a single function in my class, if the return value is void (std::function<"return value" ("parameters">). I need this to be done at compile time, and I just can't make it work as it should. Here is some test code for explanation:
#include <iostream>
#include <type_traits>
template <typename T> class Test { };
template <typename Ret, typename... Args>
class Test<Ret (Args...)>
{
public:
Ret operator()(Args...)
{
if(std::is_void<Ret>::value)
{
// Do something...
}
else /* Not a void function */
{
Ret returnVal;
return returnVal;
}
}
};
int main(int argc, char * argv[])
{
Test<void (char)> test;
test('k');
}
As you can clearly see, if the compiler does not remove the 'else' branch in the above test, my code will try to create a void value (i.e void returnVal;). The problem is that the compiler does not remove the branch so I end up with a compiler error:
./test.cpp: In instantiation of ‘Ret Test::operator()(Args ...) [with Ret = void; Args = {char}]’:
./test.cpp:27:10: required from here ./test.cpp:18:8: error:
variable or field ‘returnVal’ declared void ./test.cpp:19:11: error:
return-statement with a value, in function returning 'void'
[-fpermissive]
One would normally use std::enable_if combined with std::is_void, the problem is that I don't want to specialize on the function template, but on the class template.
template <typename Ret, typename... Args>
class Test<Ret (Args...)>
{
public:
typename std::enable_if<!std::is_void<Ret>::value, Ret>::type
Ret operator()(Args...)
{
Ret returnVal;
return returnVal;
}
typename std::enable_if<std::is_void<Ret>::value, Ret>::type
Ret operator()(Args...)
{
// It's a void function
// ...
}
};
If I use the above code instead I end up with even more errors and without a solution
./test.cpp:11:2: error: expected ‘;’ at end of member declaration
./test.cpp:11:2: error: declaration of ‘typename std::enable_if<(! std::is_void<_Tp>::value), Ret>::type Test<Ret(Args ...)>::Ret’
./test.cpp:6:11: error: shadows template parm ‘class Ret’
./test.cpp:11:24: error: ISO C++ forbids declaration of ‘operator()’ with no type [-fpermissive]
./test.cpp:18:2: error: expected ‘;’ at end of member declaration
./test.cpp:18:2: error: declaration of ‘typename std::enable_if<std::is_void<_Tp>::value, Ret>::type Test<Ret(Args ...)>::Ret’
./test.cpp:6:11: error: shadows template parm ‘class Ret’
./test.cpp:18:24: error: ISO C++ forbids declaration of ‘operator()’ with no type [-fpermissive]
./test.cpp:18:6: error: ‘int Test<Ret(Args ...)>::operator()(Args ...)’ cannot be overloaded
./test.cpp:11:6: error: with ‘int Test<Ret(Args ...)>::operator()(Args ...)’
./test.cpp: In member function ‘int Test<Ret(Args ...)>::operator()(Args ...)’:
./test.cpp:22:2: warning: no return statement in function returning non-void [-Wreturn-type]
./test.cpp: In instantiation of ‘int Test<Ret(Args ...)>::operator()(Args ...) [with Ret = void; Args = {char}]’:
./test.cpp:28:10: required from here
./test.cpp:13:7: error: variable or field ‘returnVal’ declared void
./test.cpp: In member function ‘int Test<Ret(Args ...)>::operator()(Args ...) [with Ret = void; Args = {char}]’:
./test.cpp:15:2: warning: control reaches end of non-void function [-Wreturn-type]
I'm sorry if I'm just plain dumb, and the answer is obvious. I'm fairly new to templates and I couldn't find a suiting answer in any of the other threads/questions.
There are a few things that are not exactly clear from your description, so I will start with the most general answer.
Assuming that the template has other functions for which the behavior must be kept the same, and you only want to redefine the behavior for that particular function, the simplest answer is to split the template in two, and use inheritance to merge them. At this point you can use partial template specialization on the base template:
template <typename T, typename... Args>
struct tmpl_base {
T operator()( Args... args ) {
//generic
}
};
template <typename... Args>
struct tmpl_base<void,Args...> {
void operator()( Args... args ) {
}
};
template <typename Ret, typename... Args>
class Test<Ret (Args...)> : tmp_base<Ret,Args...> {
// do not declare/define operator(), maybe bring the definition into scope:
using tmp_base<Ret,Args...>::operator();
// Rest of the class
If this is the only function in your template, then partial specialization is a much simpler solution that does not require abusing inheritance.
One solution is partially specializing your class template.
#include <iostream>
#include <type_traits>
template <typename T> class Test { };
template <typename Ret, typename... Args>
class Test<Ret (Args...)>
{
public:
Ret operator()(Args...)
{
std::cout<<"non-void function"<<std::endl;
Ret returnVal;
return returnVal;
}
};
template <typename... Args>
class Test<void (Args...)>
{
public:
void operator()(Args...)
{
std::cout<<"void function"<<std::endl;
}
};
int main(int argc, char * argv[])
{
Test<void (char)> test;
test('k');
}