Home > Net >  std::move on const char* with perfect forwarding
std::move on const char* with perfect forwarding

Time:11-03

I have an interesting issue on the MSVC v19.28 compiler (later versions fix this problem) where a const char* being passed to a variadic template class fails to resolve correctly. If the const char* is passed to a variadic template function then there are no errors.

Here's the code for clarity:

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

Since I work on a large team, I am unable to recommend upgrading the toolchain/compiler and so I am looking for workarounds until the compiler can be updated.

One of the workaround is to use std::move("Hello world"). What is std::move doing do a const char* and what the potential side-effects?

CodePudding user response:

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

The ordinary array-to-pointer implicit conversion, and none. Pointer types don't have move constructors or move assignment operators, so "moves" are copies (of the pointer the array decayed to).

Aside: I don't think your template does what you think it does. The pack T_Args... isn't deduced in when calling Foo::foo, so you don't have a universal reference, instead it's rvalue references.

Did you mean something like

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_assignable_v<T_Args, T_Args2> && ..., "Arguments must match parameters");
        func(std::forward<T_Args2>(args)...);
    }
};

Or possibly the even simpler

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

CodePudding user response:

It's MSVC bug.
See ticket, which is a Visual Studio 2019 16.9 Bug(Resolved).
Visual Studio 2019 16.9 uses MSVC v19.28 (Wikipedia).

If your intend is to use perfect forwarding using universal reference, move the ...T_Args from the class to the function as the other answer said.

But if your intend is to restrict the parameter type when declaring a class, you can write something like below.

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

Works in MSVC 19.28 link

Or maybe just use std::is_invocable.

  • Related