Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does g++ fail init of std::function<> from type with conversion operator and inaccessible function call operators?

This code fails with g++ 4.9 and later (including build from svn current,) but compiles without warning with clang++ and microsofts compiler (from VS2015.)

#include <functional>

struct A {
  void operator()() const {}
};
struct B {
  void operator()() const {}
};
struct C : private A, private B
{
  operator std::function<void()>() const { return nullptr; }
};

int main()
{
  std::function<void()> f{C{}};
}

The construction of f in main() fails due to operator() being ambiguous in struct C.

Why does g++ consider this to be ambiguous? The function call operators in C are privately inherited and not accessible. Adding a private, or explicitly deleted, void operator()() const to struct C makes the code compile and use the conversion operator as intended. Why would these inaccessible operators not cause problems when the inaccessible inherited ones do?

like image 321
rollbear Avatar asked Nov 20 '16 16:11

rollbear


People also ask

What is static_ cast<> in c++?

The static_cast operator converts variable j to type float . This allows the compiler to generate a division with an answer of type float . All static_cast operators resolve at compile time and do not remove any const or volatile modifiers.

What are conversion operators in C++?

Conversion Operators in C++ C++ supports object oriented design. So we can create classes of some real world objects as concrete types. Sometimes we need to convert some concrete type objects to some other type objects or some primitive datatypes. To make this conversion we can use conversion operator.

Is static_cast a function?

static_cast is actually an operator, not a function.


2 Answers

Constructor: template<class F> function(F f);

C++11:

f shall be Callable for argument types ArgTypes and return type R.

C++14:

Shall not participate in overload resolution unless f is Callable for argument types ArgTypes... and return type R.


In C++11, this constructor template is a better match than the conversion sequence involving the std::function move constructor and your user-defined conversion operator. So overload resolution selects the constructor template, which then fails to compile because f is not Callable.

In C++14, the constructor template is subject to a substitution failure, because f is not Callable. So the constructor template does not participate in overload resolution, and the best remaining match is the conversion sequence involving the std::function move constructor and your user-defined conversion operator, which is therefore used.


Clang compiles your testcase in both C++11 and C++14 mode. GCC rejects your testcase in both C++11 and C++14 mode. Here is another testcase that demonstrates the same problem in GCC:

#include <type_traits>

struct A {
  void operator()() const {}
};
struct B {
  void operator()() const {}
};
struct C : A, B {};

template <typename F, typename = std::result_of_t<F&()>>
void test(int) {}

template <typename F>
void test(double) {}

int main() {
  test<C>(42);
}

test(int) should not participate in overload resolution because std::result_of_t<F&()> should be a substitution failure, so test(double) should be called. However, this code fails to compile in GCC.

This is the same problem seen in your testcase, because this is the same mechanism used to implement SFINAE in the constructor template of std::function in libstdc++.

like image 104
Oktalist Avatar answered Oct 17 '22 17:10

Oktalist


The difference between clang++ and g++ seems to be because of some fuzziness of SFINAE:

...

A substitution failure is any situation when the type or expression above would be ill-formed (with a required diagnostic), if written using the substituted arguments.

Only the failures in the types and expressions in the immediate context of the function type or its template parameter types are SFINAE errors. If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors.

Now, std::function<R(Args...)> provides a template constructor

template<class F> function( F f );

which (through utilization of SFINAE)

does not participate in overload resolution unless f is Callable for argument types Args... and return type R.

struct C from your example is not a Callable type, because of the ambiguity of operator() (the fact that the latter is inaccessible in C doesn't play any role at this stage of overload resolution). Thus, depending on the implementation of the is-this-type-callable check in the Standard Library your code may or may not fail to compile. This can explain the difference between g++ and msvc, but not the difference between clang++ and g++, which, using the same Standard Library, work differently - clang++ compiles your code and g++ doesn't. I don't exclude the possibility that the C++ standard defines strictly which of them is correct, however IMHO it is beyond what most C++ programmers should be able to figure out on their own.

like image 34
Leon Avatar answered Oct 17 '22 17:10

Leon