在 C++11 的模板化函数中处理 void 变量

Handling a void variable in a templatized function in C++11

我有一个模板 class,它必须在调用其参数和 return 类型为通用的函数之前执行一些操作。

这是方法:

template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
  // prepare for call
  // ...
  ReturnType rv = makeCall(args...);  // [1]
  // dismiss the call
  // ...
  return rv;
}

当然,当 ReturnType 不是 void 时编译正确。 当我在这种情况下使用它时:

function<void>(firstArg, secondArg);

编译器响应

error: return-statement with a value, in function returning 'void' [-fpermissive]

指向标有[1]的行。

除了将 -fpermissive 传递给编译器之外,还有其他解决方案吗? 我更愿意有一个独特的方法,因为我找到的可能的解决方案是使用 enable_ifis_same.

实例化不同的版本

提前致谢。

-- 更新--

这是一个完整的例子。我应该说我们的功能确实是 class 方法。

#include <type_traits>
#include <iostream>

class Caller {
public:
    Caller() {}

    template <typename ReturnType, typename ...Arguments>
    ReturnType call(Arguments ... args) {
        prepare();

        ReturnType rv = callImpl<ReturnType>(args...);

        done();

        return rv;
    }

private:
    void prepare() {
        std::cout << "Prepare\n";
    }

    void done() {
        std::cout << "Done\n";
    }

    template <typename ReturnType, typename ...Arguments>
    typename std::enable_if<std::is_same<ReturnType, void>::value, ReturnType>::type callImpl ( Arguments ... args) {
        std::cout << "Calling with void\n";
        return;
    }

    template <typename ReturnType, typename ...Arguments>
    typename std::enable_if<std::is_same<ReturnType, bool>::value, ReturnType>::type callImpl (Arguments ... args) {
        std::cout << "Calling with bool\n";
        return true;
    }

    template <typename ReturnType, typename ...Arguments>
    typename std::enable_if<std::is_same<ReturnType, int>::value, ReturnType>::type callImpl (Arguments ... args) {
        std::cout << "Calling with int\n";
        return 42;
    }
};


int main(int argc, char *argv[]) {

    Caller c;
    auto rbool = c.call<bool> (1,20);
    std::cout << "Return: " << rbool << "\n";
    auto rint = c.call<int> (1,20);
    std::cout << "Return: " << rint << "\n";

    // the next line fails compilation. compile with --std=c++11
    c.call<void>("abababa");

    return 0;
}

-- 更新--

不是大问题:使用 std::bind(&Caller::callImpl<ReturnType>, this, args)

这是我对通用的 C++11 兼容解决方案的尝试,您可以轻松地重复使用它。

让我们从创建一个简单的 type trait 开始,它将 void 转换为空结构。这不会引入任何代码重复。

struct nothing { };

template <typename T>
struct void_to_nothing 
{
    using type = T;
};

template <>
struct void_to_nothing<void>
{
    using type = nothing;
};

template <typename T>
using void_to_nothing_t = typename void_to_nothing<T>::type; 

我们还需要一种方法来调用任意函数,将最终的 void return 类型转换为 nothing:

template <typename TReturn>
struct helper
{
    template <typename TF, typename... Ts>
    TReturn operator()(TF&& f, Ts&&... xs) const
    {
        return std::forward<TF>(f)(std::forward<Ts>(xs)...);
    }
};

template <>
struct helper<void>
{
    template <typename TF, typename... Ts>
    nothing operator()(TF&& f, Ts&&... xs) const
    {
        std::forward<TF>(f)(std::forward<Ts>(xs)...);
        return nothing{};
    }
};

template <typename TF, typename... Ts>
auto with_void_to_nothing(TF&& f, Ts&&... xs)
    -> void_to_nothing_t<
           decltype(std::forward<TF>(f)(std::forward<Ts>(xs)...))>
{
    using return_type = 
        decltype(std::forward<TF>(f)(std::forward<Ts>(xs)...));

    return helper<return_type>{}(std::forward<TF>(f), std::forward<Ts>(xs)...);
}

用法:

template <typename ReturnType, typename ...Args>
void_to_nothing_t<ReturnType> function (Args ...args) {
  // prepare for call
  // ...
  auto rv = with_void_to_nothing(makeCall, args...);  // [1]
  // dismiss the call
  // ...
  return rv;
}

live wandbox example


Matt Calabrese 提出了一项名为 "Regular Void" 的提案,可以解决此问题。 You can find it here: "P0146R1".

取决于您希望在行中完成什么

// dismiss the call

您或许可以使用:

template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
  // prepare for call
  // ...

  CallDismisser c;
  return  makeCall(args...);  // [1]
}

只要 CallDismisser 的析构函数可以完成您需要做的所有事情,这就可以了。

struct nothing {};


template<class Sig>
using returns_void = std::is_same< std::result_of_t<Sig>, void >;

template<class Sig>
using enable_void_wrap = std::enable_if_t< returns_void<Sig>{}, nothing >;
template<class Sig>
using disable_void_wrap = std::enable_if_t< !returns_void<Sig>{}, std::result_of_t<Sig> >;

template<class F>
auto wrapped_invoker( F&& f ) {
  return overload(
    [&](auto&&...args)->enable_void_wrap<F(decltype(args)...)> {
      std::forward<F>(f)(decltype(args)(args)...);
      return {};
    },
    [&](auto&&...args)->disable_void_wrap<F(decltype(args)...)> {
      return std::forward<F>(f)(decltype(args)(args)...);
    }
  );
}

所以 wrapped_invoker 接受一个函数对象,并使它成为 return nothing 而不是 void.

接下来,holder

template<class T>
struct holder {
  T t;
  T&& get()&& { return std::forward<T>(t); }
};
template<>
struct holder<void> {
  template<class T>
  holder(T&&) {} // discard
  void get()&& {}
};

holder 允许您保留 return 值并在需要时转换回 void。您必须使用 {} 创建 holder<T> 才能使引用生命周期延长正常工作。添加一个 ctor 到 holder<T> 会破坏它。

holder<void> 默默地丢弃传递给它的任何东西。

template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
  // prepare for call
  // ...
  holder<ReturnType> rv{ wrapped_invoker(makeCall)(args...) };
  // dismiss the call
  // ...
  return std::move(rv).get();
}

现在,holder<ReturnType> 不包含任何内容或 makeCall(args...) 的 return 值。

如果它什么都不包含,rv.get() return 无效,return 对 ReturnValuevoid 的函数无效是合法的。

基本上我们在做两个技巧。首先,我们要防止 makeCall 从 return 到 void,其次,如果我们要从 return 到 void,我们将丢弃 return 的值makeCall 有条件地。

overload 没有写在这里,但它是一个函数,它接受 1 个或多个函数对象(例如 lambdas)和 returns 它们的重载集。 std::overload 有一个提案,Whosebug 本身有无数的例子。

这是一些:

问题似乎出在 //Dismiss the call 上。

此代码不应存在。这就是我们拥有 RAII 的目的。以下代码确实有效,即使使用 ReturnType = void.

template <typename ReturnType, typename ...Arguments>
ReturnType call(Arguments ... args) {
    Context cx;
    return callImpl<ReturnType>(args...);
}

Context::Context()  { std::cout << "prepare\n"; }
Context::~Context() { std::cout << "done\n"; }