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

Viewed 159

Below is a minimal code which 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"), " ");
}

As per this post, it's a bug in MSVC which is not yet fixed: Visual Studio 2017 - could not deduce template argument (with variadic templates)

Question: What is the workaround to fix it for this specific case?


Update: This bug isn't fixable, & I am open to changing of design/interface. You are welcome to post it as an answer. Will accept the best one.

3 Answers

By adding the container type you want to the template parameter list of StringTo and then taking the function as a generic type allows you to use a lambda in StringToVector to forward to the correct member function. That would look like

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

And you can see it working on Rextester here: https://rextester.com/BLSS95194

A more lean design might just do:

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

Would that be a usable work-around ?


Another approach could be to use a type based customization design from the get-go instead (example using 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

Workaround 1

Make the problematic parameter as the first type of the template and pass it explicitly.

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


Workaround 2

If we compromise on the Type part and use directly std::string, then this error goes away in MSVC as well.

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.

Related