Home > OS >  How can I iterate over variadic template parameters in c ?
How can I iterate over variadic template parameters in c ?

Time:12-02

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

CodePudding user response:

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)

CodePudding user response:

You could:

  • Search for a pattern to replace.
  • 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.

I've put all this logic into another function, ReplaceImpl, and left Replace to do the early checks (e.g., 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.

CodePudding user response:

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