Question

I'm having a problem with an assignment of mine. The question for the assignment is as follows:

Write a function template named Interpolate that will make the below work. Each argument will be output when its corresponding % is encountered in the format string. All output should be ultimately done with the appropriate overloaded << operator. A \% sequence should output a percent sign.

    SomeArbitraryClass obj;
    int i = 1234;
    double x = 3.14;
    std::string str("foo");
    std::cout << Interpolate(R"(i=%, x1=%, x2=%\%, str1=%, str2=%, obj=%)", i, x, 1.001, str,  "hello", obj) << std::endl;

If there is a mismatch between the number of percent signs and the number of arguments to output, throw an exception of type cs540::WrongNumberOfArgs.

Now, I've started to write the code to make it work. However, I'm running into a problem using non-PODs. Here is what I have written so far:

#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>

std::string Interpolate(std::string raw_string) {
    std::size_t found = raw_string.find_first_of("%");
    if(found != std::string::npos && raw_string[found-1] != '\\') {
        std::cout << "Throw cs540::ArgsMismatchException" << std::endl;
    }
    return raw_string;
}


template <typename T, typename ...Args>
std::string Interpolate(std::string raw_string, T arg_head, Args... arg_tail) {
    std::size_t found = raw_string.find_first_of("%");
    while(found != 0 && raw_string[found-1] == '\\') {
        found = raw_string.find_first_of("%", found + 1);
    }
    if(found == std::string::npos) {
        std::cout << "Throw cs540::ArgsMismatchException." << std::endl;
    }

    // Checking the typeid of the arg_head, and converting it to a string, and concatenating the strings together. 
    else {
        if(std::is_arithmetic<T>::value) { 
            raw_string = raw_string.substr(0, found) + std::to_string(arg_head) + raw_string.substr(found + 1, raw_string.size());
        }
    }
    return Interpolate(raw_string, arg_tail...);
}

int main(void) {
    int i = 24332;
    float x = 432.321;
    std::string str1("foo");
    //Works
    std::cout << Interpolate(R"(goo % goo % goo)", i, x) << std::endl;
    // Does not work, even though I'm not actually doing anything with the string argument
    std::cout << Interpolate(R"(goo %)", str1) << std::endl;
}
Was it helpful?

Solution

This is a run time check semantically. This means that the code in the {} is compiled, even if the expression is always false:

   if(std::is_arithmetic<T>::value) { 
        raw_string = raw_string.substr(0, found) + std::to_string(arg_head) + raw_string.substr(found + 1, raw_string.size());
    }

to fix this, you can do this:

template<typename T>
void do_arithmetic( std::string& raw_string, T&& t, std::true_type /* is_arthmetic */ ) {
  raw_string = raw_string.substr(0, found) + std::to_string(std::forward<T>(t)) + raw_string.substr(found + 1, raw_string.size());
}
template<typename T>
void do_arithmetic( std::string& raw_string, T&& t, std::false_type /* is_arthmetic */ ) {
  // do nothing
}

then put in your code:

do_arithmetic( raw_string, arg_head, std::is_arithmetic<T>() );

which does a compile-time branch. The type of std::is_arithmetic is either true_type or false_type depending on if T is arithmetic. This causes different overloads of do_arithmetic to be called.

In C++1y you can do this inline.

template<typename F, typename...Args>
void do_if(std::true_type, F&& f, Args&&... args){
  std::forward<F>(f)( std::forward<Args>(args)... );
}
template<typename...Args>
void do_if(std::false_type, Args&&...){
}
template<bool b,typename...Args>
void do_if_not(std::integral_constant<bool,b>, Args&& args){
  do_if( std::integral_constant<bool,!b>{}, std::forward<Args>(args)... );
}
template<typename C, typename F_true, typename F_false, typename...Args>
void branch( C c, F_true&&f1, F_false&& f0, Args&&... args ){
  do_if(c, std::forward<F_true>(f1), std::forward<Args>(args)... );
  do_if_not(c, std::forward<F_false>(f0), std::forward<Args>(args)... );
}

which is boilerplate. We can then do in our function:

do_if(std::is_arithmetic<T>{},
  [&](auto&& arg_head){
    raw_string = raw_string.substr(0, found) + std::to_string(arg_head) + raw_string.substr(found + 1, raw_string.size());
  },
  arg_head
);

or, if you want both branches:

branch(std::is_arithmetic<T>{},
  [&](auto&& x){
    raw_string = std::to_string(x); // blah blah
  }, [&](auto&&) {
    // else case
  },
  arg_head
);

and the first method only gets instantianted with x=arg_head if is_arithmetic is true.

Needs polish, but sort of neat.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top