【问题标题】:How can I iterate over variadic template parameters in c++?How can I iterate over variadic template parameters in c++?
【发布时间】:2022-12-02 01:09:56
【问题描述】:

I'm trying to use variadic templates (for the first time really) to make a string replacement function.

Essentially, I want to make a function (we'll call it Replace) that takes a key value that is used to search up and modify a template string based on the additional parameters provided in the Replace function. The template string has placeholders using the string "%s".

My problem is, I'm not sure how to iterate through the variadic template parameters... Here is some sample code.

const std::string FindOriginal(std::string Key)
{
    if(Key == "Toon")
    {
        return "This is example %s in my program for Mr. %s.";
    }
    
    return "";
}

std::string Replace(std::string OriginalKey) {
    return "";
}

template<typename First, typename ... Strings>
std::string Replace(std::string OriginalKey, First arg, const Strings&... rest)
{
    const std::string from = "%s";
    std::string the_string = FindOriginal(OriginalKey);
    
    int i = 0;
    size_t start_pos = the_string.find(from);
    while(start_pos != std::string::npos)
    {
        // Ideally here I can somehow get the parameter at index i... 
        the_string.replace(start_pos, from.length(), rest[i]);
        
        start_pos = the_string.find(from, start_pos);
        i++;
    }
    
    Replace(rest...);
    
    return the_string;
}

int main()
{
    std::cout << Replace("Toon", "5", "Jimmy") << std::endl;    
}

【问题讨论】:

  • I'm not really following your code, but the usual technique is to call you variadic function recursively with one fewer argument. That way you iterate over the arguments from left to right. But indexing can be done, I would look for an example as it's complicated.
  • Add your expected output and how it differs from current output. If you're getting any error, post that error.
  • Also, templates are compile time constructs but function parameter Key is not a constexpr.
  • Thanks @john the "recursively" bit cleared things up for me... I'll post what I came up with.

标签: c++ c++11 variadic-templates


【解决方案1】:

Thanks to someone's comment regarding variadic templates working through recursive calls, I managed to change the solution to have the desired outcome. Basically we feed the parameters to a templated function (GetModifiedString) that calls the variadic template function (ReplaceValue).

#include <iostream>
#include <string>

const std::string FindOriginal(const std::string& Key)
{
    if(Key == "Toon")
    {
        return "This is example %s in my program for Mr. %s.";
    }
    
    return "";
}

void ReplaceValue(std::string& OriginalString)
{
    (void)OriginalString;
}

template<typename First, typename ... Strings>
void ReplaceValue(std::string& OriginalString, First arg, const Strings&... rest)
{
    const std::string from = "%s";

    size_t start_pos = OriginalString.find(from);
    if(start_pos != std::string::npos)
    {
        OriginalString.replace(start_pos, from.length(), arg);
    }
    
    ReplaceValue(OriginalString, rest...);
}

template<typename First, typename ... Strings>
std::string GetModifiedString(const std::string& OriginalKey, First arg, const Strings&... rest)
{
    std::string modified_string = FindOriginal(OriginalKey);
    ReplaceValue(modified_string, arg, rest...);
   
    return modified_string;
}

int main()
{
    std::cout << GetModifiedString("Toon", "5", "Jimmyy") << std::endl;    
}

【讨论】:

    【解决方案2】:

    If you have access to C++17 or later, your logic is best expressed with a fold expression which can apply a function to your string for each parameter in the pack (unary left fold):

    template<class T>
    const std::string& ReplaceOne(std::string& originalString, const T& replacement)
    {
        static constexpr auto sentinel = "%s";
        size_t start_pos = originalString.find(sentinel);
        if(start_pos != std::string::npos)
            originalString.replace(start_pos, 2, replacement);
        return originalString;
    }
    
    template<class...Ts>
    void Replace(std::string& originalString, const Ts&... rest)
    {
        (ReplaceOne(originalString, rest) , ...);
    }
    

    Called like so:

    std::string original = "This is example %s in my program for Mr. %s.";
        const std::string expected = "This is example 5 in my program for Mr. Jimmy.";
    Replace(original, "5", "Jimmy");
    assert(original == expected);
    

    Live Demo

    Essentially we are calling ReplaceOne against our string to replace a single instance of "%s" with the next variadic argument. If no "%s" is found, we just return the string unchanged.

    We use a unary left fold so that we work on the parameters left-to-right because order matters.

    The downside to an approach like this one (and indeed the approach in your answer) is that you are calling std::string::find for each variadic argument, which potentially searches the entire string. In the worst case, no instances of "%s" are even in your string, and as a result, you search the entire string for each replacement. This is inefficient; O(N * M) where N is the length of your string, and M is the number of arguments in your parameter pack.

    We can get our time complexity down to O(N) if we repeat calls to find starting from the last result of find. I'll leave that as an exercise to you for now.

    (Another alternative is to store your replacement strings in a temporary container and iterate over the string in a loop like Stack Danny suggested in this answer (now deleted)

    【讨论】:

      【解决方案3】:

      Regular expressions may come handy here, since you can:

      • Search for a pattern to replace. If found, regex_search will yield an input string prefix, the pattern, and a suffix.
      • Do the recursive call with the input string suffix and the rest of arguments (i.e., rest).
      • Then concatenate the input string prefix, the current arg, and whatever the recursive call returned.
      • And return that.

      [Demo]

      #include <iostream>
      #include <regex>
      #include <string>
      
      const std::string FindOriginal(std::string Key) {
          if (Key == "Toon") {
              return "This is example %s in my program for Mr. %s.";
          }
          return {};
      }
      
      std::string ReplaceImpl(std::string) {
          return {};
      }
      
      template <typename First, typename... Strings>
      std::string ReplaceImpl(std::string str, First arg, const Strings&... rest) {
          std::regex pattern{ R"(%s)" };
          std::smatch matches{};
          if (std::regex_search(str, matches, pattern)) {
              return
                  matches.prefix().str() +
                  arg +
                  ReplaceImpl(matches.suffix().str(), rest...);
          }
          return str;
      }
      
      template <typename First, typename... Strings>
      std::string Replace(std::string OriginalKey, First arg,
                          const Strings&... rest) {
          const std::string from = "%s";
          std::string the_string = FindOriginal(OriginalKey);
      
          if (not the_string.empty()) {
              return ReplaceImpl(the_string, arg, rest...);
          }
          return {};
      }
      
      int main() {
          std::cout << Replace("Toon", "5", "Jimmy") << "
      ";
      }
      
      // Outputs: This is example 5 in my program for Mr. Jimmy
      

      【讨论】:

        猜你喜欢
        • 2022-11-20
        • 2022-12-01
        • 2022-12-01
        • 2022-12-27
        • 2022-12-27
        • 2022-12-01
        • 2022-12-01
        • 2022-12-01
        • 2022-12-19
        相关资源
        最近更新 更多