#include <cstdio>

#include <string>
#include <tuple>
#include <utility>

template <typename T>
struct get_specifier;

template <>
struct get_specifier<int> {
    static constexpr char const* value = "%d";
};

template <>
struct get_specifier<char const*> {
    static constexpr char const* value = "%s";
};

template <>
struct get_specifier<double> {
    static constexpr char const* value = "%f";
};

template <size_t, typename>
struct format_runner;

template <size_t Index, typename... Ts>
struct format_runner<Index, std::tuple<Ts...>> {
    using tuple_type = std::tuple<Ts...>;
    
    template <typename String>
    static String execute(String&& init) {
        return format_runner<Index-1, tuple_type>::execute(std::forward<String>(init))
               + " "
               + get_specifier<
                  typename std::tuple_element<Index, tuple_type>::type
               >::value;
    }
};

template <typename... Ts>
struct format_runner<0, std::tuple<Ts...>> {
    using tuple_type = std::tuple<Ts...>;
    
    template <typename String>
    static String execute(String&& init) {
        return init + get_specifier<
                  typename std::tuple_element<0, tuple_type>::type
               >::value;
    }
};

template <typename String, typename... Ts>
String format(String&& init, Ts&&... values) {
    return format_runner<
        sizeof...(Ts)-1,
        std::tuple<typename std::remove_reference<Ts>::type...>
    >::execute(std::forward<String>(init));
}

template <typename... Ts>
int redisCommand(char const* prefix, Ts&&... values) {
    return std::printf(
        format(std::string(prefix), values...).c_str(),
        values...
    );
}

int main() {
    char const* value1 = "1";
    int value2 = 2;
    double value3 = 3.0;
    
    redisCommand("SET foo ", value1, value2, value3);
}