【问题标题】:std::move on const char* with perfect forwardingstd::move on const char* 完美转发
【发布时间】:2021-12-17 05:07:03
【问题描述】:

我在MSVC v19.28 编译器上遇到了一个有趣的问题(更高版本修复了这个问题),其中const char* 被传递给可变参数模板class 无法正确解决。如果将const char* 传递给可变参数模板函数,则没有错误。

为了清楚起见,这里是代码:

#include <type_traits>

template <typename... T_Args>
struct Foo
{
    template <typename T_Func>
    void foo(T_Func func, T_Args&&... args)
    {
        func(std::forward<T_Args>(args)...);
    }
};

template <typename T_Func, typename... T_Args>
void bar(T_Func func, T_Args&&... args)
{
    func(std::forward<T_Args>(args)...);
}

int main()
{
    bar([](int, float, const char* c){ }, 5, 5.0f, "Hello world");

    // <source>(26): error C2672: 'Foo<int,float,const char *>::foo': no matching overloaded function found
    // <source>(26): error C2440: 'initializing': cannot convert from 'const char [12]' to 'const char *&&'
    // <source>(26): note: You cannot bind an lvalue to an rvalue reference
    Foo<int, float, const char*> f;
    f.foo([](int, float, const char* c){ }, 5, 5.0f, "Hello world");

    // this compiles, but what are the repurcussions of std::move() on a string literal?
    Foo<int, float, const char*> g;
    g.foo([](int, float, const char* c){ }, 5, 5.0f, std::move("Hello world"));
}

由于我在一个大型团队工作,我无法推荐升级工具链/编译器,因此我正在寻找解决方法,直到可以更新编译器。

其中一种解决方法是使用std::move("Hello world")std::move 在做什么?const char* 有什么潜在的副作用?

【问题讨论】:

  • 另一种解决方法:+"Hello world"Demo
  • 你真的想要右值引用吗(由类固定,没有转发引用)。
  • 注:你不是在打电话给bar&lt;lambda, int, float, const char *&gt;,你是在打电话给bar&lt;lambda, int, float, const char(&amp;)[12]&gt;
  • @Caleth 对 - 我不完全理解为什么 const char(&amp;)[12] 不能衰减到 const char* - 但我理解为什么在成员函数调用中没有推断出参数包,这是对我的疏忽部分

标签: c++ variadic-templates perfect-forwarding


【解决方案1】:

std::moveconst char [12] 做了什么?潜在的副作用是什么?

普通的数组到指针的隐式转换,没有。指针类型没有移动构造函数或移动赋值运算符,因此“移动”是(数组衰减到的指针的副本)。

旁白:我不认为你的模板做你认为它做的事。调用Foo::foo 时不会推导出包T_Args...,因此您没有通用引用,而是右值引用。

你的意思是像

template <typename... T_Args>
struct Foo
{
    template <typename T_Func, typename... T_Args2>
    void foo(T_Func func, T_Args2&&... args)
    {
        static_assert(std::is_constructible_v<T_Args, T_Args2> && ..., "Arguments must match parameters");
        func(std::forward<T_Args2>(args)...);
    }
};

或者可能更简单

struct Foo
{
    template <typename T_Func, typename... T_Args>
    void foo(T_Func func, T_Args&&... args)
    {
        func(std::forward<T_Args>(args)...);
    }
};

【讨论】:

  • “所以“移动”是副本”。但只是指针,而不是整个数组:)
  • 这很好用 (+1),除了当我们处理 const char*is_assignableis_convertible 时都失败了 - 我想这完全是另一个问题:godbolt.org/z/coqnMKTcs跨度>
  • @Samaursa is_constructible 在这里可能更正确
【解决方案2】:

这是 MSVC 错误。
请参阅ticket,这是一个Visual Studio 2019 16.9 Bug(已解决)。
Visual Studio 2019 16.9 使用 MSVC v19.28 (Wikipedia)。

如果您打算使用通用引用使用完美转发,请将 ...T_Args 从类移动到函数,如另一个答案所述。

但是如果你打算在声明一个类时限制参数类型,你可以写如下。

#include <type_traits>
#include <utility>

template<typename ...Args>
struct Foo {
  template<typename F, typename ...Args2>
  std::enable_if_t<(std::is_convertible_v<Args2, Args> && ...)>
  foo(F func, Args2&&... args)
  {
    func(std::forward<Args2>(args)...);
  }
};


int main() {

  Foo<int, float, const char*> f;
  f.foo([](int, float, const char* c){ }, 5, 5.0f, "Hello world");
  
  // invalid type!
  //f.foo([](int, float, const char* c){ }, 5, "", "Hello world");

  return 0;
}

适用于 MSVC 19.28 link

或者只使用std::is_invocable

【讨论】:

    猜你喜欢
    • 2015-09-07
    • 2015-04-11
    • 2021-03-21
    • 1970-01-01
    • 2014-01-04
    • 1970-01-01
    • 1970-01-01
    • 2012-06-23
    • 1970-01-01
    相关资源
    最近更新 更多