#include <iostream>

//
// Shim interface
//
struct Interface {
    virtual void print(std::ostream& out) const = 0;
}; // struct Interface

std::ostream& operator<<(std::ostream& out, Interface const& i) {
    i.print(out);
    return out;
}

template <typename T>
struct IT: public Interface {
    IT(T const& t): _t(t) {}
    virtual void print(std::ostream& out) const { out << _t; }
    T const& _t;
};

template <typename T>
IT<T> shim(T const& t) { return IT<T>(t); }

//
// printf (or it could be!)
//
void printf_impl(char const*, std::initializer_list<Interface const*> array) {
    typedef std::initializer_list<Interface const*>::const_iterator It;
    for (It it = array.begin(), end = array.end(); it != end; ++it) { std::cout << **it; }
    std::cout << "\n";
}

template <typename... T>
void printf_bridge(char const* format, T const&... t) {
    printf_impl(format, {(&t)...});
}

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_bridge(format, ((Interface const&)shim(t))...);
}

//
// Usage
//
struct Pair { int first; int second; };
std::ostream& operator<<(std::ostream& out, Pair const& p) {
    return out << "(" << p.first << ", " << p.second << ")";
}

int main() {
    Pair const p = { 1, 2 };
    printf("unused", 1, 4, p);
}