How to handle variable number of arguments and string variables

81 views Asked by At

In the following code, the variable number of arguments assume a string variable in itself but it does not happen for string literals

#include <iostream>
#include <string>
#include <memory>

template <typename MyType, typename StringT=std::string, typename... ArgsT,
            typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>>
  std::shared_ptr<MyType> my_function(const StringT& name, ArgsT&&... args) {
      std::cout << "name: " << name << std::endl;
      auto t = std::make_shared<MyType>(5);
      return t;
  }
  
template <typename MyType, typename... ArgsT>
  std::shared_ptr<MyType> my_function(ArgsT&&... args) {
      auto t = my_function<MyType>("default name", std::forward<ArgsT>(args)...);
      return t;
  }

int main() {
    std::string myvar= "something";
    auto ret = my_function<int>("something");
    auto ret2 = my_function<int, std::string>(myvar);
    auto ret3 = my_function<int>(myvar);
    auto ret4 = my_function<int>(42);// 42 could be anything

    return 0;
}

Prints:

name: something
name: something
name: default name
name: default name

How to avoid this and call the first constructor in the third my_function<int>(myvar) call as well? How to print "something" in all the cases except the last one?

2

There are 2 answers

0
Remy Lebeau On BEST ANSWER

You are using SFINAE to enable the 1st function only if std::string can be constructed from the 1st parameter type. So, you should then use SFINAE to disable the 2nd function for the same parameter type, eg:

#include <iostream>
#include <string>
#include <memory>

template <typename MyType, typename StringT=std::string, typename... ArgsT,
            typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>>
  std::shared_ptr<MyType> my_function(const StringT& name, ArgsT&&... args) {
      std::cout << "name: " << name << std::endl;
      auto t = std::make_shared<MyType>(5);
      return t;
  }
  
template <typename MyType, typename Arg1T, typename... ArgsT,
            typename = std::enable_if_t<!std::is_constructible_v<std::string, Arg1T>>>
  std::shared_ptr<MyType> my_function(Arg1T&& arg1, ArgsT&&... args) {
      auto t = my_function<MyType>("default name", std::forward<Arg1T>(arg1), std::forward<ArgsT>(args)...);
      return t;
  }

int main() {
    std::string myvar= "something";
    auto ret = my_function<int>("something");
    auto ret2 = my_function<int, std::string>(myvar);
    auto ret3 = my_function<int>(myvar);
    auto ret4 = my_function<int>(42);// 42 could be anything

    return 0;
}

Output:

name: something
name: something
name: something
name: default name

Online Demo

If you need to handle the case where no input parameters are passed in, then simply add a 3rd overload for that case:

template <typename MyType>
  std::shared_ptr<MyType> my_function() {
      auto t = my_function<MyType>("default name");
      return t;
  }

Online Demo

2
user12002570 On

The problem is that for the call my_function<int>(myvar) the generic version my_function(ArgsT&&... args) is better match because it doesn't require a conversion to const for it's argument. That is, the first overload my_function(const StringT& name, ArgsT&&... args) requires a conversion to const for its first parameter name and hence it is worse match than the generic version my_function(ArgsT&&... args).

To show this, below is a contrived example:

#include <iostream>
template<typename T, typename... Args> void func(const T&, Args&&...)
{
std::cout << "const T& version";
}
template<typename... Args> void func(Args&&...)
{
 std::cout << "Without const version";
}
int main()
{
    func(4);   //prints Without const version
} 

Solution

One way to solve this is to pass name by value by removing the low-level const from name as shown below:

template <typename MyType, typename StringT=std::string, typename... ArgsT,
            typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>>
//------------------------------------vvvvvvv------------------------->removed const & from here
  std::shared_ptr<MyType> my_function(StringT name, ArgsT&&... args) {
      std::cout << "name: " << name << std::endl;
      auto t = std::make_shared<MyType>(5);
      return t;
  }

Working demo