Take a reference if lvalue and make a copy if rval

2019-07-29 07:43发布

问题:

I'm pretty new in move and lvalue semantics. And I have the impression I'm doing it wrong. Here the code I want to be able to write once FunctContainer is implemented:

std::function<double(double)> f = [](double x){return (x * x - 1); };

FunctContainer fc1 = FunctContainer(f);

FunctContainer fc2 = FunctContainer([](double x){return (x * x - 1); });

I want to write FunctContainer's ctors so that the lifetime of the function stored in fc1 is the one of f and the lifetime in fc2 of the contained function is the lifetime of fc2 itself.

I have written something (see below) but I'm not really satisfied (I got it wrong).

This is correct c++ but wrong behavior: f_ expires after the call to the constructor when f_ is an rvalue.

class FunctContainerWRONG{
public:
  IntegrandNotProjecting(const std::function<double(double)>& function)
    : f_(function){}
  IntegrandNotProjecting(const std::function<double(double)>&& function)
    : f_(std::move(function)){}
  const std::function<double(double)>& f_;
private:
};

This looks awful at me and probably is not correct c++ but is intended to explain in pseudocode what the desired behavior looks like. If possible I want to avoid to constuct a brand new object and I just want to let my object "persist":

class FunctContainer{
public:
  FunctContainer(const std::function<double(double)>& function)
    : f_p(nullptr), 
      f_(function){}
  FunctContainer(const std::function<double(double)>&& function)
    : f_p()std::make_shared<std::function<double(double)>>(function)), 
      f_(*f_p){}
private:
    std::shared_ptr<std::function<double(double)>> f_p;
    const std::function<double(double)>& f_;
};

回答1:

Forwarding references and reference collapsing rules can help you achieve this easily.

template <typename T>
struct FunctContainer 
{
    T _f;

    template <typename TFwd>
    FunctContainer(TFwd&& f) : _f{std::forward<TFwd>(f)} { }
};

template <typename T>
auto makeFunctContainer(T&& f) -> FunctContainer<T>
{
    return {std::forward<T>(f)};
}
  • When you invoke makeFunctContainer with an lvalue, T will be an lvalue reference. This means that you'll store an lvalue reference inside FucntContainer.

  • When you invoke makeFunctContainer with an rvalue, T will be a value. This means that you'll store a value inside FucntContainer.


Example usage:

auto fc0 = makeFunctContainer(std::move(f0)); // `fc0` owns `f0`
auto fc1 = makeFunctContainer(f1); // `fc1` stores a reference to `f1`


回答2:

Since C++17 there is class template argument deduction with user-defined deduction guides (Class Template Argument Deduction). With that you can solve your problem as follows:

#include <utility>
#include <iostream>

template<typename FnT>
struct FunctContainer {
    constexpr FunctContainer(FnT f) : f_ { std::forward<FnT>(f) } {}

    FnT f_;
};

template<typename FnT>
FunctContainer(const FnT& f) -> FunctContainer<const FnT&>;

template<typename FnT>
FunctContainer(FnT&& f) -> FunctContainer<FnT>;

int
main(
) {
    auto f { [] { std::cout << "Hello, world!" << std::endl; } };

    FunctContainer f1 { f }; // stores reference to f in f1
    FunctContainer f2 { [] { std::cout << "Hello, world!" << std::endl; } }; // moves f into f2

    f1.f_();
    f2.f_();

    return 0;
}