Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conversion from nullptr_t to type with implicit conversion from std::function

I've encountered the following situation that fails in several compilers but works in others. I'm wondering if the code is valid or not according to the C++11 standard. We have a type taking std::function<void()> as the sole argument constructor and try to make a std::pair containing it, passing nullptr as the argument for it's position in the std::pair:

#include <functional>
#include <utility>

struct Foo {
  Foo(std::function<void ()> f = nullptr) { }
};

typedef std::pair< void*, Foo > TestPair;

int main(void) {
  Foo f(nullptr); // always works
  //f = nullptr;  // never works
  TestPair p1(nullptr, static_cast< std::function<void()> >(nullptr)); // works
  TestPair p2(nullptr, nullptr); // fails in some compilers
  return 0;
}

You can test it here with different compilers. It compiles in VS 2015, GCC >= 6.1, and Clang >= 3.4. It fails in the Clang bundled with XCode 8 (clang-800.0.42.1), Clang 3.3, and GCC 5.4. Each gives some variation on:

candidate constructor not viable: no known conversion from 'nullptr_t' to 'const Foo' for 2nd argument
like image 378
Jake Cobb Avatar asked Apr 20 '26 04:04

Jake Cobb


1 Answers

You need a conversion constructor which convert from nullptr_t to Foo :

#include <functional>
#include <utility>

struct Foo {
  Foo(std::function<void ()> f = nullptr) { }
  Foo(std::nullptr_t) {}
};

typedef std::pair< void*, Foo > TestPair;

int main(void) {
  Foo f(nullptr); // always works
  //f = nullptr;  // never works
  TestPair p1(nullptr, static_cast< std::function<void()> >(nullptr)); // works
  TestPair p2(nullptr, nullptr); // fails in some compilers
  return 0;
}

MSVC sometimes doesn't require a copy constructor for copy initialization.


Foo f = nullptr;

Without a proper conversion constructor, this will require two user defined conversion for implicit converting from std::nullptr_t to Foo (the compiler will not do this): from std::nullptr_t to std::function<???> and then to Foo. The first converting might work under c++17 for other circumstance, but not in this case.


The std::pair's constructor is forwarding nullptr to Foo's constructor, and something changed in the implementation of the standard library.

Sorry for the delay. I had a little adventure in the awesome error messsssssage and in the the source code of the standard library of GNU.

Here follows the forwarding constructor for std::pair in g++-6 and g++-5:

// source code from /usr/include/c++/6/bits/stl_pari.h
template<typename _U1, 
         typename _U2, 
         typename
  enable_if<
    _MoveConstructiblePair<_T1, _T2, _U1, _U2>() &&
      _ImplicitlyMoveConvertiblePair<_T1, _T2, _U1, _U2>(),
    bool
  >::type=true>
constexpr pair(_U1&& __x, _U2&& __y)
    : first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }

template<typename _U1,
         typename _U2,
         typename
  enable_if<
    _MoveConstructiblePair<_T1, _T2, _U1, _U2>() &&
      !_ImplicitlyMoveConvertiblePair<_T1, _T2, _U1, _U2>(),
    bool
  >::type=false>
explicit constexpr pair(_U1&& __x, _U2&& __y)
    : first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }

// source code from /usr/include/c++/5/bits/stl_pari.h
template<class _U1,
         class _U2,
         class = typename
  enable_if<
    __and_<is_convertible<_U1, _T1>,is_convertible<_U2, _T2>>::value
  >::type>
constexpr pair(_U1&& __x, _U2&& __y)
    : first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }

// error message from g++ 5.4
// This constructor failed us. 
In file included from /usr/include/c++/5/bits/stl_algobase.h:64:0,
                 from /usr/include/c++/5/memory:62,
                 from main.cc:1:
/usr/include/c++/5/bits/stl_pair.h:144:12: note: candidate: template<class _U1, class _U2, class> constexpr std::pair<_T1, _T2>::pair(_U1&&, _U2&&)
  constexpr pair(_U1&& __x, _U2&& __y)
            ^
/usr/include/c++/5/bits/stl_pair.h:144:12: note:   template argument deduction/substitution failed:
/usr/include/c++/5/bits/stl_pair.h:141:38: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
       template<class _U1, class _U2, class = typename

The reason why g++-5 doesn't accept the code is because that std::nullptr_t and Foo isn't convertible.

g++-6's _MoveConstructiblePair is roughly something for telling whether we can construct an object from another object. And in this case, constructible.

Conclusion: the forwarding constructor of std::pair changed his requirement from Convertible to MoveConstructible.

like image 63
felix Avatar answered Apr 22 '26 16:04

felix



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!