Если вы хотите использовать шаблоны, я не думаю, что вам действительно нужны variadic шаблоны. Если вы готовы перекомпилировать, чтобы включить или выключить вход в систему:
struct NoLogging {
void log(const graph_partition &) {}
};
struct Logging {
std::vector<graph_partition> vec;
void log(const graph_partition &p) {
vec.push_back(p);
}
};
template <typename Logger>
void some_algorithm(Logger &logger) {
// do some stuff
logger.log(something);
}
// optionally, for convenience
void some_algorithm() {
NoLogging l;
some_algorithm(l);
}
// user writes:
some_algorithm();
// or
Logging l;
some_algorithm(l);
// do something with l.vec
Разница между этим и «просто протоколировать по умолчанию, даже если мне это не нужно», в том, что даже смутно приличный компилятор полностью удалит вызовы log
в some_algorithm<NoLogging>
, потому что он может видеть, что они ничего не делают.
Если вы не хотите перекомпилировать, вы можете иметь переключатель времени выполнения между двумя различными наборами экземпляров - это может быть или не быть удобным делать через некоторый полиморфный интерфейс, который предоставляет все алгоритмы и имеет два производные классы, из шаблона, например, так:
template <typename Logger>
struct ConcreteAlgorithms : public Algorithms {
Logger logger;
static void some_algorithm() {
::some_algorithm(logger);
}
// more algorithms
};
Algorithms *get_algorithms(bool with_logging) {
if (with_logging) {
return new ConcreteAlgorithms<Logging>;
} else {
return new ConcreteAlgorithms<NoLogging>;
}
}
Однако в этот момент у вас будет раздувание кода двух разных версий алгоритмов, поэтому вы можете предпочесть сделать регистратор полиморфным и вместо этого взять (возможно, крошечные) накладные расходы, как и в ответе Марка.