如何克服成员方法指针模板实参推导失败的MSVC bug?

How to overcome the MSVC bug of failing deduction of template argument of pointer to member method?

下面是一个最小的代码 compiles in g++, but gives error in MSVC:

template<typename Type,
         typename Return,  // <--- error: this is not deduced
         typename Container,
         typename Parameter>
Container
StringTo (Type&& copy,
          const char tokens[],
          Return (Container::*Insert) (const Parameter&))
{
  static_assert(not std::is_lvalue_reference<Type>::value, "Must be rvalue.");
  Container container;
  return container;
}

template<typename Type>
auto
StringToVector (Type&& copy,
                const char tokens[])
{
  static_assert(not std::is_lvalue_reference<Type>::value, "Must be rvalue.");
  return StringTo(std::move(copy), tokens, &std::vector<Type>::push_back); // <--- here
}

int main()
{
  auto v = StringToVector(std::string("hello world"), " ");
}

根据此 post,这是 MSVC 中的一个错误,尚未修复:Visual Studio 2017 - could not deduce template argument (with variadic templates)

问题:针对此特定情况,解决此问题的解决方法是什么?


更新:此错误无法修复,我愿意更改 design/interface。欢迎您将 post 作为答案。会接受最好的。

一个更精益的设计可能只是:

template<typename Type>
auto
StringToVector (Type&& copy,
                const char tokens[])//what are we going to use this for ?
{
  return std::vector{ copy };
}

Try it yourself on godbolt

这会是一个可用的解决方法吗?


另一种方法可能是从一开始就使用基于类型的定制设计 相反(例如使用 std::back_insert_iterator

#include <utility>
#include <string>
#include <vector>

template<typename Container,
         typename Inserter,
         typename Type
         >
Container
StringTo (Type&& copy,
          const char tokens[])
{
  static_assert(!std::is_lvalue_reference<Type>::value, "Must be rvalue.");
  Container container;
  *Inserter(container) = copy;
  return container;
}

template<typename Type>
auto
StringToVector (Type&& copy,
                const char tokens[])
{
  static_assert(!std::is_lvalue_reference<Type>::value, "Must be rvalue.");
  return StringTo<std::vector<Type>, std::back_insert_iterator<std::vector<Type> > >(std::move(copy), tokens); 
}

int main()
{
  auto v = StringToVector(std::string("hello world"), " ");
}

Try it yourself

通过将你想要的容器类型添加到StringTo的模板参数列表中,然后将函数作为泛型类型允许你在StringToVector中使用lambda来转发到正确的成员功能。看起来像

template<typename Container,
         typename Type,
         typename Func>
Container
StringTo (Type&& copy,
          const char tokens[],
          Func func)
{
  static_assert(not std::is_lvalue_reference<Type>::value, "Must be rvalue.");
  Container container;
  func(container, std::move(copy));
  return container;
}

template<typename Type>
auto
StringToVector (Type&& copy,
                const char tokens[])
{
  static_assert(not std::is_lvalue_reference<Type>::value, "Must be rvalue.");
  return StringTo<std::vector<Type>>(std::move(copy), tokens, [](auto& cont, auto&& val){ cont.push_back(std::move(val)); } ); // <--- here
}

int main()
{
  auto v = StringToVector(std::string("hello world"), " ");
}

您可以在此处看到它在 Rextester 上运行:https://rextester.com/BLSS95194

解决方法 1

将有问题的参数作为模板的第一个类型并显式传递。

template<typename Parameter,
         typename Return,
         typename Container>
Container
StringTo (Parameter&& copy,
          const char tokens[],
          Return (Container::*Insert) (const Parameter&))
{
  Container container;
  // do something ...
  return container;
}

template<typename Type>
auto
StringToVector (Type&& copy,
                const char tokens[])
{
  return StringTo<Type>(std::move(copy), tokens, &std::vector<Type>::push_back);
} //             ^^^^^^ here (explicit)                       ^^^^ passed anyways

Demo


解决方法 2

如果我们在 Type 部分妥协并直接使用 std::string,那么这个错误在 MSVC 中也会消失。

template<typename Return,
         typename Container>
Container
StringTo (std::string&& copy, // Type --> std::string
          const char tokens[],
          Return (Container::*Insert) (const std::string&))
{
  Container container;
  // do something ...
  return container;
}

inline
auto
StringToVector (std::string&& copy,
                const char tokens[])
{
  return StringTo(std::move(copy), tokens, &std::vector<std::string>::push_back);
}

Demo.