Einige allgemeine Code-Manipulationsfunktionen müssen je nachdem, ob eine Funktion einen Rückgabewert hat oder nicht, unterschiedlich funktionieren. Zum Beispiel, ein Problem von this question ausleihend, sagen wir, dass wir eine time_it
Funktion schreiben müssen, nimmt eine Funktion und einige Argumente, führt sie aus und druckt die verstrichene Zeit aus. Der folgende Code kann dies tun:Vermeidung von Wiederholungen bei SFINAE Unterscheidung zwischen ungültigen und nicht voiden Rückgabetypen
#include <chrono>
#include <type_traits>
#include <cmath>
#include <iostream>
template<class Fn, typename ...Args>
auto time_it(Fn fn, Args &&...args) ->
typename std::enable_if<
!std::is_void<typename std::result_of<Fn(decltype(std::forward<Args>(args))...)>::type>::value,
typename std::result_of<Fn(decltype(std::forward<Args>(args))...)>::type>::type
{
const auto start = std::chrono::system_clock::now();
auto const res = fn(std::forward<Args>(args)...);
const auto end = std::chrono::system_clock::now();
std::cout << "elapsed " << (end - start).count() << std::endl;
return res;
}
template<class Fn, typename ...Args>
auto time_it(Fn fn, Args &&...args) ->
typename std::enable_if<
std::is_void<typename std::result_of<Fn(decltype(std::forward<Args>(args))...)>::type>::value,
void>::type
{
const auto start = std::chrono::system_clock::now();
fn(std::forward<Args>(args)...);
const auto end = std::chrono::system_clock::now();
std::cout << "elapsed " << (end - start).count() << std::endl;
}
int main()
{
time_it([](double x){return std::cos(x);}, 3.0);
time_it([](double x){}, 3.0);
}
Wie man sehen kann, gibt es einen Unterschied zwischen den Fällen der Funktion einen Wert zurückkehrt oder nicht. Im ersten Fall muss der Wert gespeichert, die verstrichene Zeit gedruckt und der Wert zurückgegeben werden. Im letzteren Fall muss nach dem Drucken der verstrichenen Zeit nichts mehr getan werden.
Die Frage ist, wie man mit beiden Fällen beschäftigen:
Der obige Code verwendet
std::enable_if
undis_void
, aber die erste (umständlich in sich selbst) Argument zuis_void
wird als letztes Argumentenable_if
wiederholt - das ist umständlich und smells, esp. so viel vom Körper wird wiederholt.Die vorgenannte Antwort umgeht das Problem, indem die verstrichene Zeit als Nebenprodukt eines Destruktors einer bestimmten Klasse der verstrichenen Zeitgeber ausgegeben wird. Es ist eine nette Idee, aber in komplexeren Anwendungen würde zu verschachtelten Code führen (wesentliche Arbeit wird in einem Destruktor einer separaten Klasse getan - es ist kein natürlicher Fluss).
Gibt es einen besseren Weg, dies zu tun?
Sehr allgemeines Refactoring - Thanks! –
@AmiTavory Es wurde geändert, um 'std :: experimental :: optional 'zu verwenden, das durch' boost :: optional 'ersetzt werden kann, da ich aus Sicherheitsgründen Bedenken hatte. – Yakk