Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concept checking a function doesn't work with movable-only arguments

I have a function that calls a callback function that accepts a movable-only type (for example unique_ptr).

template <typename Function>
void foo(const Function& function) {
    BOOST_CONCEPT_ASSERT((
            boost::UnaryFunction<Function, void, std::unique_ptr<Bar>));
    auto bar = std::make_unique<Bar>();
    ...
    function(std::move(bar));
}

Trying to compile this code, I get a message that the BOOST_CONCEPT_ASSERT line tries to copy the unique_ptr. If I remove the line, the code works fine. It seems that the Boost.Concept library does not support move semantics. Is there any workaround for this without writing my own concept class (which, incidentally, would not be very simple to support both lvalues and rvalues as their arguments).

like image 497
petersohn Avatar asked Aug 15 '26 13:08

petersohn


2 Answers

That's correct. Unfortunately, UnaryFunction as a concept is written as:

  BOOST_concept(UnaryFunction,(Func)(Return)(Arg))
  {
      BOOST_CONCEPT_USAGE(UnaryFunction) { test(is_void<Return>()); }

   private:
      void test(boost::mpl::false_)
      {    
          f(arg);               // "priming the pump" this way keeps msvc6 happy (ICE)
          Return r = f(arg);
          ignore_unused_variable_warning(r);
      }    

      void test(boost::mpl::true_)
      {    
          f(arg); // <== would have to have std::move(arg)
                  // here to work, or at least some kind of
                  // check against copy-constructibility, etc.
      }    

#if (BOOST_WORKAROUND(__GNUC__, BOOST_TESTED_AT(4) \
                      && BOOST_WORKAROUND(__GNUC__, > 3)))
      // Declare a dummy construktor to make gcc happy.
      // It seems the compiler can not generate a sensible constructor when this is instantiated with a refence type.
      // (warning: non-static reference "const double& boost::UnaryFunction<YourClassHere>::arg"
      // in class without a constructor [-Wuninitialized])
      UnaryFunction();
#endif

      Func f;
      Arg arg; 
  };

Since arg is passed by lvalue, there's no way to get that to work with Boost.Concepts. Directly. You could write a hack though. Since we're just calling checking that f(arg) is valid, we could construct a local type for arg that is convertible to unique_ptr<Bar>. That is:

template <typename Function>
void foo(Function f)
{
    struct Foo {
        operator std::unique_ptr<int>();
    };

    BOOST_CONCEPT_ASSERT((
            boost::UnaryFunction<Function, void, Foo>));

    f(std::make_unique<int>(42));
}

Or more generally:

template <typename T>
struct AsRvalue {
    operator T(); // no definition necessary
};

template <typename Function>
void foo(Function f)
{
    BOOST_CONCEPT_ASSERT((
            boost::UnaryFunction<Function, void, AsRvalue<std::unique_ptr<int>>>));

    f(std::make_unique<int>(42));
}

That compiles for me on gcc and clang (though gives a warning on clang about unused typedefs). However, at that point, it may be clearer to just write out your own concept to get it to work. Something like Piotr's would be easiest.

like image 176
Barry Avatar answered Aug 18 '26 03:08

Barry


#include <type_traits>
#include <utility>

template <typename...>
struct voider { using type = void; };

template <typename... Ts>
using void_t = typename voider<Ts...>::type;

template <typename, typename = void_t<>>
struct is_callable : std::false_type {};

template <typename F, typename... Args>
struct is_callable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>> : std::true_type {};

//...

static_assert(is_callable<Function&(std::unique_ptr<Bar>)>{}, "Not callable");

DEMO

like image 31
Piotr Skotnicki Avatar answered Aug 18 '26 04:08

Piotr Skotnicki



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!