简体   繁体   中英

How can I iterate over variadic template parameters in c++?

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

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 right 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 right 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)

You could:

  • Search for a pattern to replace.
  • Do the recursive call with the input string suffix and the rest of arguments (ie, rest ).
  • Then concatenate the input string prefix, the current arg , and whatever the recursive call returned.
  • And return that.

I've put all this logic into another function, ReplaceImpl , and left Replace to do the early checks (eg, find the original key and return if the original key is not found).

[Demo]

##include <iostream>
#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 str) {
    return str;
}

template <typename First, typename... Strings>
std::string ReplaceImpl(std::string str, First arg, const Strings&... rest) {
    if (auto pos{ str.find("%s") }; pos != std::string::npos) {
        return
            str.substr(0, pos) + 
            arg +
            ReplaceImpl(str.substr(pos + 2), rest...);
    }
    return str;
}

template <typename First, typename... Strings>
std::string Replace(std::string OriginalKey, First arg, const Strings&... rest) {
    if (auto the_string{ FindOriginal(OriginalKey) }; not the_string.empty()) {
        return ReplaceImpl(the_string, arg, rest...);
    }
    return {};
}

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

// Outputs: This is example 5 in my program for Mr. Jimmy.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM