I am trying to use concepts to overload templated functions whose argument depends on the template parameter. Unfortunately this fails on gcc, reporting an ambiguous overload.
In my project I have code that compiles in gcc 9.3 and fails in gcc 12.2. But in trying to minimize the code, I've ended up with this, which fails on both gcc versions, but works in clang 15.0.0:
#include <type_traits>
#include <iostream>
struct A
{
using value_type = int;
};
struct B
{
using value_type = int;
};
template<typename Candidate>
concept something = requires {
typename Candidate::value_type;
};
template<typename Candidate>
concept something_specific = something<Candidate> && std::is_same_v<Candidate, A>;
template<something T>
void foo()
{
std::cout << "Something" << std::endl;
}
template<something_specific T>
void foo()
{
std::cout << "Something specific" << std::endl;
}
template<something T>
void bar(const typename T::value_type& s)
{
std::cout << "Something: " << s << std::endl;
}
template<something_specific T>
void bar(const typename T::value_type& s)
{
std::cout << "Something specific: " << s << std::endl;
}
int main()
{
foo<B>(); // works
foo<A>(); // works
bar<B>(1); // works
bar<A>(2); // ambiguous overload
return 0;
}
See godbolt, the error message is as expected:
<source>:48:11: error: call of overloaded 'bar<A>(int)' is ambiguous
48 | bar<A>(2); // ambiguous overload
| ~~~~~~^~~
<source>:32:6: note: candidate: 'void bar(const typename T::value_type&) [with T = A; typename T::value_type = int]'
32 | void bar(const typename T::value_type& s)
| ^~~
<source>:37:6: note: candidate: 'void bar(const typename T::value_type&) [with T = A; typename T::value_type = int]'
37 | void bar(const typename T::value_type& s)
| ^~~
I expected the something_specific overload to be chosen, because the concept that's restricting the template parameter is more specific. We can see this is true for foo already, otherwise it would be ambiguous as well.
Of course I can make it work with a requires clause, but I expected it to work as-is. So I'd like to understand whether this is just not supported or if it's a compiler issue. Is clang correct in compiling this code?
Related
I've tried searching for some explanations about how this exact pattern of inheritance works, but never found anything quite similar, so I hope some of you guys know what's going on.
So here's the behaviour I want to get:
#include <iostream>
template <typename T, typename F = std::less<T>>
class Base
{
protected:
F obj;
public:
virtual bool f(T t1, T t2) { return obj(t1, t2); }
};
template <typename T>
struct Derived : public Base<T, std::less<T>>
{
public:
bool f(T t1, T t2) { return this->obj(t2, t1); }
};
int main()
{
Base<int> b;
std::cout << std::boolalpha << b.f(1, 2) << '\n';
Derived<float> d;
std::cout << std::boolalpha << d.f(1, 2) << '\n';
}
Here I redefine f() in Derived and change how the class behaves. The program prints out
true
false
just like it should.
However, when I try to modify it so that Derived could take a pair but only compare the .first fields like this
#include <iostream>
template <typename T, typename F = std::less<T>>
class Base
{
protected:
F obj;
public:
virtual bool f(T t1, T t2) { return obj(t1, t2); }
};
template <typename T>
struct Derived : public Base<std::pair<T, T>, std::less<T>>
{
public:
typedef std::pair<T, T> pair;
bool f(pair t1, pair t2) { return this->obj(t1.first, t2.first); }
};
int main()
{
Base<int> b;
std::cout << std::boolalpha << b.f(1, 2) << '\n';
Derived<int> d;
std::cout << std::boolalpha << d.f(std::make_pair(1, 2), std::make_pair(2, 2)) << '\n';
}
the compiler goes all agro on me:
file.cpp:9:41: error: no match for call to ‘(std::less<int>) (std::pair<int, int>&, std::pair<int, int>&)’
9 | virtual bool f(T t1, T t2) { return obj(t1, t2); }
| ~~~^~~~~~~~
...
/usr/include/c++/10/bits/stl_function.h:385:7: note: candidate: ‘constexpr bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = int]’
385 | operator()(const _Tp& __x, const _Tp& __y) const
| ^~~~~~~~
/usr/include/c++/10/bits/stl_function.h:385:29: note: no known conversion for argument 1 from ‘std::pair<int, int>’ to ‘const int&’
385 | operator()(const _Tp& __x, const _Tp& __y) const
| ~~~~~~~~~~~^~~
So it's clear that the Base::f() is called instead of the redefinition. But why is this happening when in the first example everything was fine and the virtual functions behaved as intended?
virtual means that either Base::f or Derived::f is called depending on the dynamic type of the object.
The fact that you only call Derived<int>::f in main does not change that Base<std::pair<int,int>,std::less<int>::f is not valid.
A much simpler example with same issue is this:
#include <utility>
struct base {
virtual std::pair<int,int> foo(std::pair<int,int> x) {
return x+x;
}
};
struct derived : base {
std::pair<int,int> foo(std::pair<int,int> x) override {
return x;
}
};
int main() {
derived d;
}
Resulting in error:
<source>: In member function 'virtual std::pair<int, int> base::foo(std::pair<int, int>)':
<source>:6:17: error: no match for 'operator+' (operand types are 'std::pair<int, int>' and 'std::pair<int, int>')
6 | return x+x;
| ~^~
| | |
| | pair<[...],[...]>
| pair<[...],[...]>
Compiler returned: 1
Depending on what you are actually trying to achieve you might want to make Base::f pure virtual, or provide a specialization of Base when T is a std::pair, or inherit from Base<std::pair<T,T>,std::less<std::pair<T,T>>>.
Why this code doesn't compile ?
#include <iostream>
#include <typeinfo>
template <typename ...Ts>
void f();
template <typename T>
void f() {
std::cout << typeid(T).name() << std::endl;
}
template <typename T, typename U, typename ...Ts>
void f() {
std::cout << typeid(T).name() << ", ";
f<U, Ts...>();
}
int main(int argc, char** argv)
{
f<int, float, char>();
}
MSVC compiler error:
error C2668: 'f': ambiguous call to overloaded function
Expected output:
int, float, char
Side question: would there be a more modern way to do the same thing ?
EDIT
I've found a way to accept zero template pack:
#include <typeinfo>
#include <iostream>
#include <type_traits>
template <typename ...Ts>
using is_empty_pack = std::enable_if_t<sizeof ...(Ts) == 0>;
template <typename ...Ts, typename = is_empty_pack<Ts...>>
void f() {}
template <typename T, typename ...Ts>
void f() {
std::cout << typeid(T).name();
if constexpr (sizeof ...(Ts) > 0) std::cout << ", "; else std::cout << std::endl;
f<Ts...>();
}
int main(int argc, char *argv[])
{
f<>();
f<int>();
f<int, float>();
}
Any other suggestion?
Compiling with g++ gives a pretty clear explanation of what's happening:
prog.cc: In function 'int main(int, char**)':
prog.cc:20:24: error: call of overloaded 'f<int, float, char>()' is ambiguous
20 | f<int, float, char>();
| ~~~~~~~~~~~~~~~~~~~^~
prog.cc:5:6: note: candidate: 'void f() [with Ts = {int, float, char}]'
5 | void f();
| ^
prog.cc:13:6: note: candidate: 'void f() [with T = int; U = float; Ts = {char}]'
13 | void f() {
You've provided three different templated functions f, two of which could match what you've written here.
EDIT: Maybe you thought the first one was a declaration and the other two are specializations, but that's not how templates work. Specialization means specializing the type or value of a particular template argument, not specializing the number of template arguments.
Deleting
template <typename ...Ts>
void f();
will make the program compile and run with the expected behavior.
I am not able to figure out why following code does not compile. In the overload 1, the type deduced is int & so, it should result in SFINAE. And we should only have one candidate function.
#include <iostream>
#include <type_traits>
template <typename T>
std::enable_if<std::is_same<T, std::remove_reference_t<T>>::value, void> foo(T&& a){
std::cout << "overload 1" << std::endl;
}
template <typename T>
void foo(T&& a){
std::cout << std::is_same<T, std::remove_reference_t<T>>::value << std::endl;
std::cout << "overload 2" << std::endl;
}
int main(){
int a = 2;
foo(a);
// std::cout << std::is_same<int, int&>::value << std::endl; ---> gives false
return 0;
}
Compilation Error:
template_with_reference.cpp: In function ‘int main()’:
template_with_reference.cpp:19:8: error: call of overloaded ‘foo(int&)’ is ambiguous
foo(a);
^
template_with_reference.cpp:5:74: note: candidate: std::enable_if<std::is_same<T, typename std::remove_reference< <template-parameter-1-1> >::type>::value, void> foo(T&&) [with T = int&; typename std::remove_reference< <template-parameter-1-1> >::type = int]
std::enable_if<std::is_same<T, std::remove_reference_t<T>>::value, void> foo(T&& a){
^~~
template_with_reference.cpp:11:6: note: candidate: void foo(T&&) [with T = int&]
Could you guys help me find the error?
Triggered by a comment to this answer I would like to write (in C++11) a
template <typename T>
struct has_out_op { static const bool value = ???; }
to dis/enable a member function depending on std::cout << t; being valid for some T t. I came this far...
#include <iostream>
struct can_convert_to_base{}; // but does not when there is a better match
struct base {base(can_convert_to_base);};
template <typename T>
auto test(const T& t,can_convert_to_base)
-> decltype( std::cout << t);
template <typename T>
std::false_type test(const T& t,base);
template <typename T>
struct has_out_op {
static const bool value =
!std::is_same<std::false_type,
decltype( test(T(),can_convert_to_base()) )
>::value;
};
struct A{};
int main() {
std::cout << has_out_op<int>::value; // prints 1
std::cout << has_out_op<A>::value; // prints 0
}
This seems to work, but when I use it for what I was actually aiming for:
struct B {
template <typename T>
typename std::enable_if<has_out_op<T>::value,B&>::type operator<<(const T& t) {
std::cout << t;
return *this;
}
};
int main() {
B b;
b << "1";
}
I get the error
prog.cc: In instantiation of 'const bool has_out_op<char [2]>::value':
prog.cc:25:60: required by substitution of 'template<class T> typename std::enable_if<has_out_op<T>::value, B&>::type B::operator<<(const T&) [with T = char [2]]'
prog.cc:31:14: required from here
prog.cc:17:67: error: functional cast to array type 'char [2]'
decltype( test(T(),can_convert_to_base()) )
^
prog.cc: In function 'int main()':
prog.cc:31:11: error: no match for 'operator<<' (operand types are 'B' and 'const char [2]')
b << "1";
^
Then I realized that my has_out_op requires T to be default constructible, and since that I am turning in circles. When I have a value I can easily test if std::cout << t; is valid, but with the type alone I have no idea how to properly implement has_out_op.
How to detect if there is a matching overload for std::cout << t; given only decltype(t)?
Note that I already know how to dis/enable B::operator<< properly, but out of courisity I am still struggling with getting has_out_op right.
std::declval<T>() to the rescue:
Converts any type T to a reference type, making it possible to use
member functions in decltype expressions without the need to go
through constructors.
Note that because no definition exists for declval, it can only be
used in unevaluated contexts; i
...
decltype( test(std::declval<T>(),can_convert_to_base()) )
...
Since we're already here, your solution is overly complicated. This is how I would do it:
struct B {
template <typename T, class = decltype(std::cout << std::declval<T>())>
B& operator<<(const T& t)
{
std::cout << t;
return *this;
}
};
though I would be interested if there is a simpler solution for
has_out_op
template <typename T>
struct has_out_op_impl
{
template <class U, class = decltype(std::cout << std::declval<U>())>
static auto foo(U) -> std::true_type;
static auto foo(...) -> std::false_type;
using Type = decltype(foo(std::declval<T>()));
};
template <class T>
struct has_out_op : has_out_op_impl<T>::Type
{};
struct A{};
int t1()
{
static_assert(has_out_op<int>::value == true, "");
static_assert(has_out_op<A>::value == false, "");
}
I am trying to create a (very) simple messaging system, but am stuck with C++03. I have solved this problem before using C++11 features, but I do not have such luxuries anymore.
The target compiler is Visual Studio 2008's (which I think is VC9?), but I do not have it with me at this time; this said I can reproduce the problem by simply forcing g++ to the C++03 standard.
I have managed to isolate the problem in the following piece of code:
testing03.cpp
#include <iostream>
#include <map>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/units/detail/utility.hpp>
struct BaseMessage {};
struct DerivedMessage : BaseMessage {};
std::map<std::string, boost::function<void(BaseMessage)>> subscribers;
template <typename Type>
void ask(boost::function<void(Type)> function)
{
std::cout << "Asking for " << boost::units::detail::demangle(typeid(Type).name()) << std::endl;
subscribers[boost::units::detail::demangle(typeid(Type).name())] = function;
}
void testBase(BaseMessage)
{
std::cout << "In testBase" << std::endl;
}
void testDerived(DerivedMessage)
{
std::cout << "In testDerived" << std::endl;
}
int main()
{
ask<BaseMessage>(boost::bind(testBase, _1));
ask<DerivedMessage>(boost::bind(testDerived, _1));
}
...and there could be any number of derived messages.
The error that stands out to me the most
no known conversion for argument 1 from ‘BaseMessage’ to ‘DerivedMessage’
Full output of running g++ -std=c++03 testing03.cpp
In instantiation of ‘static void boost::detail::function::void_function_obj_invoker1<FunctionObj, R, T0>::invoke(boost::detail::function::function_buffer&, T0) [with FunctionObj = boost::function<void(DerivedMessage)>; R = void; T0 = BaseMessage]’:
/usr/include/boost/function/function_template.hpp:934:38: required from ‘void boost::function1<R, T1>::assign_to(Functor) [with Functor = boost::function<void(DerivedMessage)>; R = void; T0 = BaseMessage]’
/usr/include/boost/function/function_template.hpp:722:7: required from ‘boost::function1<R, T1>::function1(Functor, typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type) [with Functor = boost::function<void(DerivedMessage)>; R = void; T0 = BaseMessage; typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type = int]’
/usr/include/boost/function/function_template.hpp:1069:16: required from ‘boost::function<R(T0)>::function(Functor, typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type) [with Functor = boost::function<void(DerivedMessage)>; R = void; T0 = BaseMessage; typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type = int]’
/usr/include/boost/function/function_template.hpp:1124:5: required from ‘typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, boost::function<R(T0)>&>::type boost::function<R(T0)>::operator=(Functor) [with Functor = boost::function<void(DerivedMessage)>; R = void; T0 = BaseMessage; typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, boost::function<R(T0)>&>::type = boost::function<void(BaseMessage)>&]’
testing03.cpp:18:67: required from ‘void ask(boost::function<void(Type)>) [with Type = DerivedMessage]’
testing03.cpp:34:50: required from here
/usr/include/boost/function/function_template.hpp:153:11: error: no match for call to ‘(boost::function<void(DerivedMessage)>) (BaseMessage&)’
BOOST_FUNCTION_RETURN((*f)(BOOST_FUNCTION_ARGS));
^
/usr/include/boost/function/function_template.hpp:1048:7: note: candidate is:
class function<BOOST_FUNCTION_PARTIAL_SPEC>
^
/usr/include/boost/function/function_template.hpp:761:17: note: boost::function1<R, T1>::result_type boost::function1<R, T1>::operator()(T0) const [with R = void; T0 = DerivedMessage; boost::function1<R, T1>::result_type = void]
result_type operator()(BOOST_FUNCTION_PARMS) const
^
/usr/include/boost/function/function_template.hpp:761:17: note: no known conversion for argument 1 from ‘BaseMessage’ to ‘DerivedMessage’
To be clear on what I'm asking, how can I change my ask function (which is where I believe the problem is), so that I can call functions with types derived from a common base using C++03?
You can use a helper function that performs the cast:
#include <map>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/units/detail/utility.hpp>
#include <boost/smart_ptr.hpp>
struct BaseMessage {};
struct DerivedMessage : BaseMessage {};
// we need a function to perform the `static_cast` from the base class
// to the derived class
template<class T, class F>
void invoke_binder(F const& f, BaseMessage const& m)
{
f( static_cast<T const&>(m) );
}
std::map<std::string, boost::function<void(BaseMessage const&)> > subscribers;
template <typename Type, class F>
void ask(F const& f)
{
std::cout << "Asking for "
<< boost::units::detail::demangle(typeid(Type).name())
<< std::endl;
// boost::function is a polymorphic function wrapper;
// it can store any Callable
// Here, we store the actual function to be called, `f`, inside the
// function object returned from `bind`.
// The `bind` expression returns a function object that invokes
// `invoke_binder` which invokes the bound function `f`.
subscribers[boost::units::detail::demangle(typeid(Type).name())]
= boost::bind(&invoke_binder<Type, F>, f, _1);
}
Note the change in the signature of the callbacks; also, the calls to ask now directly use the function pointer. There's some error if it's a bind expression -- and I don't know what causes it.
void testBase(BaseMessage const&)
{
std::cout << "In testBase" << std::endl;
}
void testDerived(DerivedMessage const&)
{
std::cout << "In testDerived" << std::endl;
}
template<class T>
void call(T const& p)
{
subscribers_t::const_iterator i =
subscribers.find( boost::units::detail::demangle(typeid(T).name()) );
if(i != subscribers.end())
{
(i->second)(p);
}else
{
// error handling
}
}
int main()
{
ask<BaseMessage>(&testBase);
ask<DerivedMessage>(&testDerived);
DerivedMessage d;
call(d);
BaseMessage b;
call(b);
}
A bit more complicated, but w/o bind expressions, is the following:
template<class Type, class F>
struct wrapper
{
F f;
wrapper(F const& f) : f(f) {}
void operator()(BaseMessage const& p)
{
return f( static_cast<Type const&>(p) );
}
};
template <typename Type, class F>
void ask(F const& f)
{
std::cout << "Asking for "
<< boost::units::detail::demangle(typeid(Type).name())
<< std::endl;
subscribers[boost::units::detail::demangle(typeid(Type).name())]
= wrapper<Type, F>(f);
}
And strangely enough, this works even when called with ask<BaseMessage>(boost::bind(&testBase, _1));. I suspect a wrapped binder is treated in a special way that causes the error in the first version when called like this.