std::move const char* 完美转发

std::move on const char* with perfect forwarding

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

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

#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"));
}

因为我在一个大团队工作,所以我不建议升级 toolchain/compiler,所以我正在寻找解决方法,直到可以更新编译器。

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

What is std::move doing to a const char [12] and what are the potential side-effects?

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

旁白:我认为您的模板并不像您认为的那样。调用 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)...);
    }
};

这是 MSVC 错误。
参见 ticket, which is a Visual Studio 2019 16.9 Bug(已解决)。
Visual Studio 2019 16.9 使用 MSVC v19.28 (Wikipedia).

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

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

#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.