2016-07-28 6 views
5

ich einen Merkmalsvektor haben, die verschiedene Arten halten kann:Vorlage Wählen Sie basierend auf Laufzeit Zeichenfolge in C++

class base_attribute_vector; // no template args 

template<typename T> 
class raw_attribute_vector : public base_attribute_vector; 

raw_attribute_vector<int> foo; 
raw_attribute_vector<std::string> foo; 

Basierend auf Laufzeit-Eingabe für die Art, würde Ich mag die geeignete Datenstruktur erstellen . Pseudocode:

std::string type("int"); 
raw_attribute_vector<type> foo; 

Offensichtlich schlägt dies fehl. Eine einfache, aber hässlich und wartbaren Abhilfe ist ein Laufzeitschalter/angekettet, wenn:

base_attribute_vector *foo; 
if(type == "int") foo = new raw_attribute_vector<int>; 
else if(type == "string") ... 

ich mit functors Lesen Sie mehr über Polymorphismus-Zeit laufen, aber fand es ziemlich komplex für eine Aufgabe, die vom Konzept her einfach ist.

Was ist der beste und sauberste Weg, um dies zu erreichen? Ich spielte mit boost::hana herum, festgestellt, dass, während ich eine Abbildung von String erstellen kann eingeben, kann die Suche nur bei der Kompilierung durchgeführt werden:

zur Compile-Zeit
auto types = 
hana::make_map(
    hana::make_pair(BOOST_HANA_STRING("int"), hana::type_c<int>), 
    hana::make_pair(BOOST_HANA_STRING("string"), hana::type_c<std::string>) 
); 

Alle möglichen Arten sind bekannt. Irgendwelche Vorschläge werden sehr geschätzt. In einer perfekten Lösung würde ich das Namens-> Typ-Mapping an einem einzigen Ort erstellen. Danach würde ich es so verwenden

std::vector<base_attribute_vector*> foo; 

foo.push_back(magic::make_templated<raw_attribute_vector, "int">); 
foo.push_back(magic::make_templated<raw_attribute_vector, "std::string">); 

foo[0]->insert(123); 
foo[1]->insert("bla"); 

foo[0]->print(); 
foo[1]->print(); 

Es ist nicht erforderlich, dass diese Magie zur Kompilierzeit passiert. Mein Ziel ist es, möglichst lesbaren Code zu haben.

+3

Vielleicht bin ich etwas fehlt, aber jede Vorlage Instanziierung geschieht per Definition bei compiletime, also bin ich mir nicht sicher, was du erwartest. Sie können natürlich alle Vorlagen von einer gemeinsamen Basisklasse erben lassen und dann den Laufzeitpolymorphismus verwenden, aber der Bereich der instanziierten Vorlagen wird noch bei Kompilierungszeit definiert. – MikeMB

+2

Nehmen wir an, wir haben es gelöst und Sie können zur Laufzeit die entsprechende Datenstruktur erstellen. Können Sie uns ein Beispiel zeigen, wie Sie es verwenden werden? Die Chancen stehen gut, dass Sie das genannte Problem lösen können, wenn Sie dieses Beispiel konstruieren können. – Leon

+0

@MikeMB Ja, das ist die Idee. Ich habe die Frage bearbeitet, um sie hoffentlich etwas klarer zu machen. – mrks

Antwort

4
enum class Type 
{ 
    Int, 
    String, 
    // ... 
    Unknown 
}; 

Type TypeFromString(const std::string& s) 
{ 
    if (s == "int") { return Type::Int; } 
    if (s == "string") { return Type::String; } 
    // ... 
    return Type::Unknown; 
} 

template <template <typename> class> 
struct base_of; 

template <template <typename> class C> 
using base_of_t = typename base_of<C>::type; 

Und dann die generische Fabrik

template <template <typename> class C> 
std::unique_ptr<base_of_t<C>> make_templated(const std::string& typeStr) 
{ 
    Type type = TypeFromString(typeStr); 
    const std::map<Type, std::function<std::unique_ptr<base_of_t<C>>()>> factory{ 
     {Type::Int, [] { return std::make_unique<C<int>>(); } }, 
     {Type::String, [] { return std::make_unique<C<std::string>>(); } }, 
     // ... 
     {Type::Unknown, [] { return nullptr; } } 
    }; 
    return factory.at(type)(); 
} 

eine Spezialisierung für jede Base benötigt wird:

template <> 
struct base_of<raw_attribute_vector> { 
    using type = base_attribute_vector; 
}; 

Und dann

auto p = make_templated<raw_attribute_vector>(s); 

Demo

+0

lineare Zeit-Lookup macht mich traurig :( –

+0

Die verschachtelte Vorlage von 'make_templated' war die Idee, die ich vermisste. Ich denke, ich brauche nicht die automatische Ableitung der Basisklasse und wird das auslassen, so dass ich nicht brauche Die Spezialisierung (siehe meine Antwort) Vielen Dank! – mrks

+0

@RichardHodges: Ich denke, man könnte auch die Kartenidee von Guillaume hinzufügen, um die lineare Zeitsuche zu umgehen (auf Kosten der Lesbarkeit). In unserem Fall glaube ich, dass dies der Fall sein sollte kein Leistungsproblem verursachen. – mrks

0

Sie können dies nicht tun. Im besten Fall müssen Sie eine begrenzte Anzahl von Typen unterstützen und zwischen ihnen wechseln, indem Sie eine if-Anweisung verwenden, die zur Kompilierungszeit ausgewertet werden kann.

+0

Ich habe eine begrenzte Anzahl von Typen, die zur Kompilierzeit bekannt sind. Außerdem verstehe ich, dass zur Laufzeit eine Art Wechsel stattfindet, wenn der richtige Template-Parameter gefunden werden muss. Was ich versuche ist, diesen Schalter so gut wie möglich zu verstecken. – mrks

0

Kurze Antwort: Nein, Sie können den Compiler nicht anweisen, eine Laufzeitbedingung in der Kompilierzeit auszuwerten. Nicht einmal mit Hana.

Lange Antwort: Es gibt einige (meist sprachunabhängige) Muster dafür.

Ich gehe davon aus, dass Ihre base_attribute_vector einig virtual Methode hat, höchstwahrscheinlich pure, die gemeinhin als ein interface in anderen Sprachen.

Das bedeutet, dass Sie je nach Komplexität Ihres realen Problems wahrscheinlich eine factory oder eine abstract factory möchten.

Sie könnten eine Fabrik oder abstrakte Fabrik ohne virtuelle Methoden in C++ erstellen, und Sie könnten Hana dafür verwenden. Aber die Frage ist: Lohnt sich die zusätzliche Komplexität für diesen (möglicherweise wirklich geringen) Leistungsgewinn?

(auch wenn Sie jeden virtuellen Aufruf beseitigen wollen, auch von base_attribute_vector, müssen Sie alles mit dieser Klasse eine Vorlage, nach der Einstiegspunkt machen, wo der Schalter geschieht)

Ich meine, haben Sie dies mit virtuellen Methoden implementiert und gemessen, dass die Kosten der virtuellen Aufrufe zu hoch sind?

Edit: eine andere, aber andere Lösung könnte eine Variante mit Besuchern, wie eggs::variant.

Mit variant können Sie Klassen mit Funktionen für jeden Parametertyp erstellen, und die apply-Methode schaltet die auf dem Laufzeittyp basierende Funktion um.

Etwas wie:

struct handler { 
    void operator()(TypeA const&) { ... } 
    void operator()(TypeB const&) { ... } 
    // ... 
}; 

eggs::variant<...> v; 
eggs::variants::apply(handler{}, v); 

können Sie auch als Templat Operatoren (möglicherweise mit enable_if/sfinae), wenn sie gemeinsame Teile haben.

+0

Vielleicht verstehe ich das Fabrikmuster hier falsch. Wäre es nicht notwendig, den gleichen Schalter basierend auf der Zeichenfolge zu verwenden? Wäre es nicht auf die Erstellung von raw_attribute_vectors beschränkt und benötigt eine zweite Factory, wenn ich andere Objekte basierend auf dem Typ erstellen möchte? Auswertung zur Kompilierzeit ist nicht erforderlich - Ich versuche, Code zu bekommen, der den größten Teil des Datentyps verbirgt - Hässlichkeit von Orten, wo es keinen Unterschied machen sollte. – mrks

+0

Ja, Sie müssen einen Schalter/Karte/etwas irgendwo erstellen. Die Fabrik kapselt das einfach ein - auch ich habe meine Antwort mit einer abweichenden Idee bearbeitet, es ist ein bisschen anders, aber vielleicht das, wonach Sie suchen. – Dutow

3

Ich würde eine std::map verwenden, die Zeichenfolgen als Schlüssel und std::function als Werte hat. Ich würde die Zeichenfolge mit einer Funktion verknüpfen, die Ihren Typ zurückgibt. Hier ein Beispiel:

using functionType = std::function<std::unique_ptr<base_attribute_vector>()>; 
std::map<std::string, functionType> theMap; 

theMap.emplace("int", []{ return new raw_attribute_vector<int>; }); 
theMap.emplace("float", []{ return new raw_attribute_vector<float>; }); 

// Using the map 
auto base_vec = theMap["int"](); // base_vec is an instance of raw_attribute_vector<int> 

Natürlich ist diese Lösung gültig ist, wenn Sie nur den String-Wert zur Laufzeit kennen.

+0

Große Antwort - Ich mag, wie kurz es ist. Was mir an der Antwort, die ich angenommen habe, besser gefällt, ist, dass ich denselben Code an verschiedenen Stellen wiederverwenden kann, ohne eine neue Karte erstellen zu müssen. – mrks

+0

@mrks Sie können die akzeptierte Antwort ändern. Ich stimme auch zu, dass dies die elegantere Lösung ist. –

1

Ich würde wahrscheinlich etwas tun:

Features:

  • 1 - Zeiterfassung von Objekten durch einen benannte Prototyp

  • konstante Zeit Lookup zur Laufzeit

  • vorbei
  • Suche nach jedem Typ, der mit std::string

  • verglichen werden kann

-

#include <unordered_map> 
#include <string> 


struct base_attribute_vector { virtual ~base_attribute_vector() = default; }; 

template<class Type> struct attribute_vector : base_attribute_vector {}; 

// copyable singleton makes handling a breeze  
struct vector_factory 
{ 
    using ptr_type = std::unique_ptr<base_attribute_vector>; 

    template<class T> 
    vector_factory add(std::string name, T) 
    { 
     get_impl()._generators.emplace(std::move(name), 
             []() -> ptr_type 
             { 
              return std::make_unique< attribute_vector<T> >(); 
             }); 
     return *this; 

    } 

    template<class StringLike> 
    ptr_type create(StringLike&& s) const { 
     return get_impl()._generators.at(s)(); 
    } 

private: 
    using generator_type = std::function<ptr_type()>; 

    struct impl 
    { 
     std::unordered_map<std::string, generator_type, std::hash<std::string>, std::equal_to<>> _generators; 
    }; 


private: 

    static impl& get_impl() { 
     static impl _ {}; 
     return _; 
    } 

}; 



// one-time registration 

static const auto factory = 
vector_factory() 
.add("int", int()) 
.add("double", double()) 
.add("string", std::string()); 


int main() 
{ 
    auto v = factory.create("int"); 
    auto is = vector_factory().create("int"); 

    auto strs = vector_factory().create("string"); 


} 
0

Weitgehend basiert auf Jarod42 Antwort, ist es das, was ich werden:

class base_attribute_vector {}; 

template<typename T> 
class raw_attribute_vector : public base_attribute_vector { 
public: 
raw_attribute_vector() {std::cout << typeid(T).name() << std::endl; } 
}; 

template<class base, template <typename> class impl> 
base* magic(std::string type) { 
    if(type == "int") return new impl<int>(); 
    else if(type == "float") return new impl<float>(); 
} 

int main() { 
    auto x = magic<base_attribute_vector, raw_attribute_vector>("int"); 
    auto y = magic<base_attribute_vector, raw_attribute_vector>("float"); 
} 
Verwandte Themen