防止转发“过于自由”

时间:2014-12-11 16:24:41

标签: c++ c++11 variadic-templates enable-if

我已经实现了一个执行"就地对象回收的功能"基于GotW#23中显示的令人遗憾的只是普通错误标题按位置分配新示例的想法,并在#28中引用了hack。

(是的,是的,我知道......但是这个hack实际上在一个处理大量消息的应用程序中作为一个非常有用的优化非常有用,如果使用得当,我认为它足够安全。 )

代码在没有警告的情况下编译clang ++ 3.5和"工作正常",但在我看来它有些过于自由,因为它允许一些隐含的转换可能会意外发生并且可能是不合需要的:

#include <type_traits>

template<typename T>
typename std::enable_if<std::is_nothrow_move_constructible<T>::value, T&>::type
recycle(T& obj, T&& other)  noexcept
{ obj.~T(); new (&obj) T(other);  return obj; }

template<typename T, typename... A>
typename std::enable_if<std::is_nothrow_constructible<T>::value && std::is_nothrow_move_constructible<T>::value, T&>::type
recycle(T& obj, A&&... arg) noexcept
{ obj.~T(); new (&obj) T(arg...); return obj; }


int main()
{
    struct foo
    {
        foo() noexcept {}
        explicit foo(float) noexcept {}
    };

    foo b;
    recycle(b, foo());     // OK, calls #1, move-constructing from default temporary
    recycle(b, foo(1.0f)); // OK, as above, but non-default temporary
    recycle(b, 5.0f);      // OK, calls #2, forwarding and move-constructing
    recycle(b, 5.0);       // succeeds calling #2 (undesired implicit double --> float conversion)
    recycle(b, 5);         // succeeds calling #2 (undesired implicit int ---> float conversion)
    recycle(b, b);         // succeeds, but should probably fail a runtime check (undefined behavior)

    return 0;
}

有些调用编译得恰到好处的事实可能是因为模板参数包捕获了所有内容&#34;但是我仍然感到惊讶它是如何工作的总而言之,foo的构造函数是explicit,并且模板必须调用它。我不确定如何将整数和双精度转换成浮点数,而不会发出clang ++发出的警告。

这让我想知道我是否假设explicit关键字对构造函数的作用有误。

自我分配(将调用未定义的行为)对我来说不是问题,因为尝试回收一个对象本身就显示出一些严重的大脑失败(所以我决定不添加运行时检查那个),但是意外地调用具有隐式可转换参数的函数是可以非常容易地发生的事情,并且如果可能的话,我想抓住编译器错误。

基本上我可能需要将get_constructor_args<T>放在另一个enable_if is_convertible内。或者其他......概念?

有没有办法用C ++ 11(或C ++ 14,如果你愿意)实现这种功能?

1 个答案:

答案 0 :(得分:1)

template<class T, class...Args>
using safe_to_recycle = std::integral_constant<bool,
  std::is_nothrow_constructible<T, Args...>::value
  && std::is_nothrow_destroyable<T>::value
>;


template<typename T, typename... Args>
std::enable_if_t<safe_to_recycle<T,Args...>::value, T&>
recycle(T& obj, Args&&... args)  noexcept
{
  obj.~T();
  new (&obj) T{std::forward<Args>(args)...};
  return obj;
}

template<typename T, typename... Args>
std::enable_if_t<safe_to_recycle<T,Args...>::value, T&>
recycle_explicit(T& obj, Args&&... args)  noexcept
{
  obj.~T();
  new (&obj) T(std::forward<Args>(args)...);
  return obj;
}

template<typename T, typename U>
std::enable_if_t<
  std::is_same<std::decay_t<T>,std::decay_t<U>>::value &&
  safe_to_recycle<T,U>::value,
  T&
>
recycle(T& obj, U&& rhs)  noexcept
{
  if (&obj == &rhs) return obj;
  obj.~T();
  new (&obj) T{std::forward<U>(rhs)};
  return obj;
}

template<typename T, typename U>
std::enable_if_t<
  std::is_same<std::decay_t<T>,std::decay_t<U>>::value &&
  safe_to_recycle<T,U>::value,
  T&
>
recycle_explicit(T& obj, U&& rhs)  noexcept
{
  if (&obj == &rhs) return obj;
  obj.~T();
  new (&obj) T(std::forward<U>(rhs));
  return obj;
}

如果您缺少C ++ 14,请编写自己的enable_if_tdecay_t

如果需要,您可以删除该运行时检查。只需消除最后2次重新开始。 variardic将处理移动分配等。

请注意recycle不会调用显式构造函数 - 您必须recycle( obj, T(5.0f) )recycle_explicit会缩小转化次数。