Failure at disambiguating overloaded method - c++

What follows is reduced from a system that stores pointers to methods along with their parameter's types. The user just provides type::method and the template machinery does the rest. When the method is overloaded the user must provide the signature of the desired method.
This was working very well until we tried it with some boost::asio stuff. The following code demonstrates the problem:
#include <boost/asio.hpp>
using namespace boost::asio::ip;
using namespace boost::system;
template <typename TT, typename MFP, MFP> struct OpM;
template <typename TR, typename TT, typename ... Ts, TR (TT::*f)(Ts...)>
struct OpM<TT, TR (TT::*)(Ts...), f> {};
using sig = error_code (tcp::socket::*)(const tcp::endpoint&, error_code&);
struct RM {
template <class C, typename R, typename ... Ps>
RM(R (C::*)(Ps...)) {
typedef OpM<C, R (C::*)(Ps...), &tcp::socket::connect> OP;
}
} MRegisterer(static_cast<sig>(&tcp::socket::connect));
g++ 8.3 fails to compile with the message:
g++ -std=c++17 -c connect.cpp
connect.cpp: In instantiation of 'RM::RM(R (C::*)(Ps ...)) [with C = boost::asio::basic_stream_socket<boost::asio::ip::tcp>; R = boost::system::error_code; Ps = {const boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>&, boost::system::error_code&}]':
connect.cpp:19:40: required from here
connect.cpp:17:46: error: conversion from 'boost::system::error_code (boost::asio::basic_socket<boost::asio::ip::tcp>::*)(const endpoint_type&, boost::system::error_code&)' {aka 'boost::system::error_code (boost::asio::basic_socket<boost::asio::ip::tcp>::*)(const boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>&, boost::system::error_code&)'} to 'boost::system::error_code (boost::asio::basic_stream_socket<boost::asio::ip::tcp>::*)(const boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>&, boost::system::error_code&)' in a converted constant expression
typedef OpM<C, R (C::*)(Ps...), &tcp::socket::connect> OP;
^~
connect.cpp:17:46: error: could not convert template argument '& boost::asio::basic_socket<boost::asio::ip::tcp>::connect' from '<unresolved overloaded function type>' to 'boost::system::error_code (boost::asio::basic_stream_socket<boost::asio::ip::tcp>::*)(const boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>&, boost::system::error_code&)'
It is weird that the error message refers to a conversion error from ... boost::asio::basic_socket ... to ... boost::asio::basic_stream_socket ... (and something similar for the endpoint parameter).
I'm providing the full type of the method, in RM seems to work fine but when the method is passed to OpM apparently the compiler gets confused.
What is wrong?
For completeness' sake, this is the output of clang++ 8.0:
~/bin/clang++ -std=c++17 -c connect.cpp
connect.cpp:17:37: error: conversion from '<overloaded function type>' to
'boost::system::error_code
(boost::asio::basic_stream_socket<boost::asio::ip::tcp>::*)(const
boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> &,
boost::system::error_code &)' is not allowed in a converted constant
expression
typedef OpM<C, R (C::*)(Ps...), &tcp::socket::connect> OP;
^~~~~~~~~~~~~~~~~~~~~
connect.cpp:19:3: note: in instantiation of function template specialization
'RM::RM<boost::asio::basic_stream_socket<boost::asio::ip::tcp>,
boost::system::error_code, const
boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> &,
boost::system::error_code &>' requested here
} MRegisterer(static_cast<sig>(&tcp::socket::connect));
^
1 error generated.

Here's a short reproduction of the same issue without Boost.Asio or even overloaded functions:
struct B {
void foo();
};
struct D : B { };
template <typename T, T> struct X { };
using T = X<void (D::*)(), &D::foo>; // error
The problem is, the type of &D::foo, despite being spelled D::, is actually void (B::*)(). And that type is not implicitly convertible to void (D::*)().
The nice thing for you is that since you're using C++17, you don't actually have to go through this explicit typing rigamarole, you can just write:
template <auto F> struct X { };
using T = X<&D::foo>; // fine
Or rework the whole thing to use pointers to function instead, and turn your pointer-to-member function into a function taking a D* (which you can do with a lambda or write out a function template and use an explicit specialization of it).

Related

Is there a workaround for implicit template argument deduction when none are provided?

So far I don't know if it possible at all, but I'd be glad if it was.
So the context of the question is a way of declaring an API in a way to make a function more self-explanatory. I would take for example ASIO in particular.
The way it declares asynchronous functions is kind of ambigous and relies on the comments or documentation:
/**
* #CompletionTokenT - completion token or a callable with operator()(size_t, asio::error_type)
*/
template <typename CompletionTokenT>
constexpr auto some_async_func(params, CompletionTokenT &&token);
I think it would be more convenient and consistent to have types or return arguments as a part of the funciton signature. For example:
template <typename TokenT>
constexpr auto some_async_func(params, completion<TokenT, size_t, asio::error_type> completion);
This makes requirements for a completion callable more obvious. BUT. Now it becomes very inconvenient to use, since a compiler would not be able to deduce the types.
I tried experimenting with an intermediate class with template implicit conversion operator, like
template <typename TokenT>
struct token_impl
{
template <typename ... ArgsT>
operator completion<TokenT, ArgsT...>();
};
But the completion_token has to be deduced first in order to make an implicit conversion, so it does not work (duh).
Is there some hack to circumvent implicit deduction? So the following would compile:
some_async_func(params, make_completion([](size_t, asio::error_type){});
some_async_func(params, make_completion(asio::use_future));
Another option might be to use type erasure and provide a wrapper completion<int, asio::error_type>. But that would require additional dynamic allocations.
What magnitude of overhead would that option have in comparison with a tempate typename CompletionTokenT version?
Here is one workaround that will enable the ease of use, but require a library provider to add a helper function:
template <template <typename, typename...> class TCompletionTokenT, typename TokenT, typename ... T>
constexpr auto some_async_func(params p, TCompletionTokenT<TokenT, T...> &&completion)
{
return some_async_func(p, completion<TokenT, int>(std::forward<TCompletionTokenT<TokenT, T...>>(completion)));
}
Here is an example which does not compile (at least with MinGW64)
template <typename TokenT>
struct completion_token{};
template <typename TokenT, typename ... Args>
class completion
{
public:
completion(completion_token<TokenT>)
{}
};
template <typename TokenT>
void deduce(completion<TokenT, int>)
{}
struct use_sync{};
int main() {
deduce(completion_token<use_sync>());
}
Errors:
C:\msys64\mingw64\bin\c++.exe -g -std=gnu++14 -MD -MT CMakeFiles/completion_token.dir/main.cpp.obj -MF CMakeFiles\completion_token.dir\main.cpp.obj.d -o CMakeFiles/completion_token.dir/main.cpp.obj -c ../main.cpp
../main.cpp: In function 'int main()':
../main.cpp:119:40: error: no matching function for call to 'deduce(completion_token<use_sync>)'
119 | deduce(completion_token<use_sync>());
| ^
../main.cpp:111:6: note: candidate: 'template<class TokenT> void deduce(completion<TokenT, int>)'
111 | void deduce(completion<TokenT, int>)
| ^~~~~~
../main.cpp:111:6: note: template argument deduction/substitution failed:
../main.cpp:119:40: note: 'completion_token<use_sync>' is not derived from 'completion<TokenT, int>'
119 | deduce(completion_token<use_sync>());
| ^
ninja: build stopped: subcommand failed.
This makes me curious why substitution for a candidate failed since there is a constructor.

Why do my SFINAE expressions no longer work with GCC 8.2?

I recently upgraded GCC to 8.2, and most of my SFINAE expressions have stopped working.
The following is somewhat simplified, but demonstrates the problem:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename std::enable_if<
std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename std::enable_if<
!std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
C++ (gcc) – Try It Online
C++ (clang) – Try It Online
Older versions of GCC (unfortunately I don't remember the exact version I had installed previously) as well as Clang compile the above code just fine, but GCC 8.2 gives an error stating:
: In function 'int main()':
:29:19: error: call of overloaded 'test()' is ambiguous
c.test();
^
:12:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value>::type ... = {}]'
void test() {
^~~~
:22:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value)>::type ... = {}]'
void test() {
^~~~
:30:25: error: call of overloaded 'test()' is ambiguous
c.test();
^
:12:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value>::type ... = {}]'
void test() {
^~~~
:22:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value)>::type ... = {}]'
void test() {
As is usually the case when different compilers and compiler versions handle the same code differently I assume I am invoking undefined behavior. What does the standard have to say about the above code? What am I doing wrong?
Note: The question is not for ways to fix this, there are several that come to mind. The question is why this doesn't work with GCC 8 - is it undefined by the standard, or is it a compiler bug?
Note 2: Since everyone was jumping on the default void type of std::enable_if, I've changed the question to use int instead. The problem remains.
Note 3: GCC bug report created
This is my take on it. In short, clang is right and gcc has a regression.
We have according to [temp.deduct]p7:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. [...]
This means that the substitution has to happen whether or not the pack is empty or not. Because we are still in the immediate context, this is SFINAE-able.
Next we have that a variadic parameter is indeed considered an actual template parameter; from [temp.variadic]p1
A template parameter pack is a template parameter that accepts zero or more template arguments.
and [temp.param]p2 says which non-type template parameters are allowed:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
a type that is literal, has strong structural equality ([class.compare.default]), has no mutable or volatile subobjects, and in which if there is a defaulted member operator<=>, then it is declared public,
an lvalue reference type,
a type that contains a placeholder type ([dcl.spec.auto]), or
a placeholder for a deduced class type ([dcl.type.class.deduct]).
Note that void doesn't fit the bill, your code (as posted) is ill-formed.
I am not a language lawyer, but cannot the following quote be somehow connected to the problem?
[temp.deduct.type/9]: If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi.
It seems to me that since there is no remaining argument in the template argument list, then there no comparison of the pattern (which contains enable_if). If there is no comparison, then there is also no deduction and substitution occurs after deduction I believe. Consequently, if there is no substitution, no SFINAE is applied.
Please correct me if I am wrong. I am not sure whether this particular paragraph applies here, but there are more similar rules regarding pack expansion in [temp.deduct]. Also, this discussion can help someone more experienced to resolve the whole issue: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A.
Partial answer: use typename = typename enable_if<...>, T=0 with different Ts:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename = typename std::enable_if_t<
std::is_const<typename std::remove_reference<U>::type>::value
>, int = 0
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename = typename std::enable_if_t<
!std::is_const<typename std::remove_reference<U>::type>::value
>, char = 0
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
(demo)
Still trying to figure out what the heck does std::enable_if<...>::type... mean knowing the default type is void.

C++ Concepts TS: Require nested templated type alias

For a generic library I'm trying to define a concept in terms of having a correct implementation of a traits struct. In particular I want to check that the user has provided all required nested types, static member functions and data members. However, I can't find a way to require a nested templated type(-alias).
I have a declaration of the traits struct
template <typename>
struct trait;
and a specialization for chars
template <>
struct trait<char> {
template <typename>
using type = int;
};
I now define my concept in terms of this trait
template <typename T>
concept bool SatisfiesTrait = requires() {
typename trait<T>; // require the user to have spcialized
// for their type
typename trait<T>::type<long long>; // require the nested template
};
as well as a function requiring a type satisfying this concept
constexpr bool foo(SatisfiesTrait) { return true; }
In my main method I then try to call this function with a char:
int main() {
foo('c');
}
When compiling all this with GCC I get the error message
prog.cc:15:24: error: 'trait<T>::type' is not a type
typename trait<T>::type<long long>;
^~~~
prog.cc: In function 'int main()':
prog.cc:26:11: error: cannot call function 'constexpr bool foo(auto:1) [with auto:1 = char]'
foo('c');
^
prog.cc:18:16: note: constraints not satisfied
constexpr bool foo(SatisfiesTrait) {
^~~
prog.cc:18:16: note: in the expansion of concept 'SatisfiesTrait<auto:1>' template<class T> concept const bool SatisfiesTrait<T> [with T = char]
However, when I change my main function to
int main() {
typename trait<char>::type<long long> v;
(void) v;
foo('c');
}
and comment out the requirement of the nested alias template it compiles just fine. The same problem occurs when the nested type has a non-type template parameter instead of a type parameter.
Am I doing something wrong here or is this a bug in GCCs implementation of the Concepts TS?
The code can also be found on Wandbox.

How to detect if a generic lambda is uncompilable in C++ 14?

I have a problem with detecting when an instantiation of a generic lambda is well formed but not compilable, and detecting it has stumped me:
#include <functional>
class future
{
public:
int get() & { return 5; }
};
// Gets the return type of F(A), returning a not_well_formed type if not well formed
template<class F, class A> struct get_return_type
{
struct not_well_formed {};
template<class _F, class _A> static not_well_formed test(...);
template<class _F, class _A> static auto test(_F &&f) noexcept(noexcept(f(std::declval<_A>()))) -> decltype(f(std::declval<_A>()));
using type = decltype(test<F, A>(std::declval<F>()));
static constexpr bool is_noexcept = noexcept(test<F, A>(std::declval<F>()));
};
int main(void)
{
auto foo=[](auto &&x) { return x.get(); };
using type=get_return_type<decltype(foo), const future>::type;
return 0;
}
This fails with (on clang 3.7):
ned#kate:~$ clang++-3.7 -std=c++14 -o weird_generic_lambda_thing weird_generic_lambda_thing.cpp
weird_generic_lambda_thing.cpp:21:34: error: member function 'get' not viable: 'this' argument has type 'const future', but
function is not marked const
auto foo=[](auto &&x) { return x.get(); };
^
weird_generic_lambda_thing.cpp:14:111: note: in instantiation of function template specialization 'main()::(anonymous
class)::operator()<const future>' requested here
..._F, class _A> static auto test(_F &&f) noexcept(noexcept(f(std::declval<_A>()))) -> decltype(f(std::declval<_A>()));
^
weird_generic_lambda_thing.cpp:15:25: note: while substituting explicitly-specified template arguments into function
template 'test'
using type = decltype(test<F, A>(std::declval<F>()));
^
weird_generic_lambda_thing.cpp:22:14: note: in instantiation of template class 'get_return_type<(lambda at
weird_generic_lambda_thing.cpp:21:12), const future>' requested here
using type=get_return_type<decltype(foo), const future>::type;
^
weird_generic_lambda_thing.cpp:6:7: note: 'get' declared here
int get() & { return 5; }
^
1 error generated.
You can probably blame my inexperience with Expression SFINAE here (thanks Visual Studio!), but I am surprised: surely the decltype creating the return type of test() should fail to substitute if f(std::declval<_A>()) is not well formed?
Obviously the answer is that is does fail to substitute, but in a non-SFINAE way. Can the above be fixed so it correctly returns a not_well_formed type if the generic lambda is uncompilable with some arbitrary parameter type?
You cannot in general. Only early failures can be detected via SFINAE. Early failures are basically the declaration, not the definition, of a function (or class) template.
The lambda can provide SFINAE early failure instrumentation via explicitly declaring the return type ->decltype(x.get()), or through other SFINAE techniques like enable_if_t or void_t.
The idea is that compilers are not required to fully compile functions in order to engage in overload resolution.

decltype in class template specialization

I am trying to use decltype inside a template class as follows:
#include <functional>
template <typename T>
class A
{
typedef decltype(std::bind(&A::f, std::declval<A>())) some_type;
void f();
};
That works fine, but now I'd like to add an explicit specialization:
template <>
class A<void>
{
typedef decltype(std::bind(&A::f, std::declval<A>())) some_type;
void f();
};
This time g++ gives an error:
test.cpp:14:33: error: incomplete type 'A<void>' used in nested name specifier
What am I doing wrong? I am using gcc 4.5.
EDIT: If I move the declaration of void f(); to above the typedef, as suggested by Johannes, I get (slightly) different errors:
test.cpp:15:62: error: invalid use of incomplete type 'class A<void>'
test.cpp:13:1: error: declaration of 'class A<void>'
test.cpp:15:62: error: initializing argument 2 of 'std::_Bind<typename std::_Maybe_wrap_member_pointer<_Tp>::type(_ArgTypes ...)> std::bind(_Functor, _ArgTypes ...) [with _Functor = void (A<void>::*)(), _ArgTypes = {A<void>}, typename std::_Maybe_wrap_member_pointer<_Tp>::type = std::_Mem_fn<void (A<void>::*)()>]'
test.cpp:15:62: error: invalid use of incomplete type 'class A<void>'
test.cpp:13:1: error: declaration of 'class A<void>'
test.cpp:15:62: error: initializing argument 2 of 'std::_Bind<typename std::_Maybe_wrap_member_pointer<_Tp>::type(_ArgTypes ...)> std::bind(_Functor, _ArgTypes ...) [with _Functor = void (A<void>::*)(), _ArgTypes = {A<void>}, typename std::_Maybe_wrap_member_pointer<_Tp>::type = std::_Mem_fn<void (A<void>::*)()>]'
Your order is wrong. Try exchanging it
template <>
class A<void>
{
void f();
typedef decltype(std::bind(&A::f, std::declval<A>())) some_type;
};
In the primary template, the name A::f was dependent and the compiler delayed lookup to a point where f was declared (A::f is not really dependent in C++0x anymore, since A refers to the current instantiation and therefor f to a member of the current instantiation, but as there is a loophole in the current specification (it has to do with dependent base classes), the compiler delayed the lookup nontheless). In the explicit specialization, the name is not dependent and lookup is done immediately, which is the reason you need to declare f before referring to it.
Edit: You are wrongly using std::bind. The second argument you give is of type A<void>, which will be copied/moved by std::bind into its created call wrapper object. This requires a complete type A<void>.
If you want to merely pass a reference to A on which the member function is called, you can pass a declval<A*>(), which the std::bind mechanism equally detects as magical first argument to a member pointer invocation.
But it seems to me you want to look into std::function<>, instead of doing this std::bind and decltype mess. After all you have a powerful toolset given, but by using this doubtful decltype expression, you throw away all the genericity the Standard library gives you and restrict yourself to use of that single std::bind expression. That's no good.
std::bind requires A as a complete type (see answer by Johannes) and therefore you cannot use it at this point. As a workaround, if you encapsulate the some_type this will compile:
#include <functional>
template <typename T>
class A
{
void f();
struct some_type_helper
{
typedef decltype(std::bind(&A::f, std::declval<A>())) some_type;
};
};
template <>
class A<void>
{
void f();
struct some_type_helper;
};
struct A<void>::some_type_helper
{
typedef decltype(std::bind(&A::f, std::declval<A>())) some_type;
};