Ik zit al een tijdje te stoeien met variadic templates en function overloading. Ik wil twee functies die, buiten een optionele callback parameter, dezelfde signatuur hebben. Als voorbeeld nemen we de volgende twee functies:
Waarbij de Parameter klasse zo is gemaakt dat deze een lijst van opties ondersteunt voor de constructor, bijvoorbeeld zo:
Nu zijn er dus logischerwijs twee manieren om de functie execute() aan te roepen:
Sowieso vind ik het al vreemd dat dit niet compileert: de compiler blijft eigenwijs proberen de versie van execute() te gebruiken zonder callback, merkt dat dit niet kan maar is niet in staat de andere versie te gebruiken. De enige manier waarop dat wel werkt is om zelf de lambda al te wrappen in een std::function object (wat een veel langere, onduidelijkere syntax geeft).
De enige oplossing hiervoor is om te forceren dat alle parameters uit het parameter pack geldige constructor arguments zijn voor Parameter. Hier wil ik dus std::enable_if voor gebruiken. Mijn eerste poging was als volgt:
Deze werkt echter enkel wanneer je deze aanroept met een enkel argument. enable_if houdt blijkbaar niet zo van arrays. Dus maar een simpele functie gemaakt die je met een parameter pack kunt aanroepen en die een enkele boolean retourneert. Netjes constexpr, dus dat zou moeten werken:
Dit werkt niet omdat de compiler niet begrijpt welke variant je wilt aanroepen. De tweede versie is ook geldig met een lege lijst (dit werkt enkel met variabelen, niet met types blijkbaar).
Hier zit ik dus een beetje vast. Er moeten twee variaties zijn omdat anders de compiler blijft uitrollen terwijl alle parameters op zijn (wat dus een error geeft dat er geen T meer is), maar er kunnen geen twee variaties zijn omdat de compiler dat niet slikt.
Een enkele variant zou natuurlijk leuker zijn, als je bijvoorbeeld iets als dit zou kunnen doen:
wat natuurlijk ook niet werkt omdat, ondanks dat de ternary operator er voor zorgt dat de functie niet wordt aangeroepen als er geen argumenten meer zijn, deze wel moet bestaan (en dat is natuurlijk niet zo).
Ik heb alles geprobeerd dat ik kan bedenken. Heeft iemand suggesties hoe ik dit aan de praat kan krijgen?
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| /** * Execute something, without letting us know the result. * * @param callback callback to run when execution completes * @param mixed... parameters */ template <typename ...Arguments> void execute(const std::function<void()>& callback, const Arguments& ...parameters) { // construct parameters Parameter params[sizeof...(parameters)]{ parameters... }; // execute here in another thread } /** * Execute something, without letting us know the result. * * @param mixed... parameters */ template <typename ...Arguments> void execute(const Arguments& ...parameters) { // construct parameters Parameter params[sizeof...(parameters)]{ parameters... }; // execute here in another thread } |
Waarbij de Parameter klasse zo is gemaakt dat deze een lijst van opties ondersteunt voor de constructor, bijvoorbeeld zo:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| /** * Class representing a single parameter */ class Parameter { public: /** * Constructor * * @param value parameter value */ Parameter(const char *value) {} /** * Constructor * * @param value parmeter value */ Parameter(uint64_t value) {} }; |
Nu zijn er dus logischerwijs twee manieren om de functie execute() aan te roepen:
C++:
1
2
3
4
5
6
| // this fails, because it tries to link to the second // callback type, should do something with std::enable_if execute([]() {}, "parameter 1", 2, "parameter 3"); // this works of course, all parameters can be converted to a Parameter execute("parameter 1", 2, "parameter 3"); |
Sowieso vind ik het al vreemd dat dit niet compileert: de compiler blijft eigenwijs proberen de versie van execute() te gebruiken zonder callback, merkt dat dit niet kan maar is niet in staat de andere versie te gebruiken. De enige manier waarop dat wel werkt is om zelf de lambda al te wrappen in een std::function object (wat een veel langere, onduidelijkere syntax geeft).
De enige oplossing hiervoor is om te forceren dat alle parameters uit het parameter pack geldige constructor arguments zijn voor Parameter. Hier wil ik dus std::enable_if voor gebruiken. Mijn eerste poging was als volgt:
C++:
1
| template <typename ...Arguments, class = typename std::enable_if<std::is_constructible<Parameter, Arguments>::value...>::type> |
Deze werkt echter enkel wanneer je deze aanroept met een enkel argument. enable_if houdt blijkbaar niet zo van arrays. Dus maar een simpele functie gemaakt die je met een parameter pack kunt aanroepen en die een enkele boolean retourneert. Netjes constexpr, dus dat zou moeten werken:
C++:
1
2
3
4
5
6
7
8
9
10
| /** * Constant expression to check whether all parameters * are valid constructor arguments for the requested * class. * * This does not work, as it is ambiguous, the second * version is also valid with an empty parameter pack. */ template <typename T> constexpr bool validParameter() { return std::is_constructible<Parameter, T>::value; } template <typename T, typename ...More> constexpr bool validParameter() { return std::is_constructible<Parameter, T>::value && validParameter<More...>(); } |
Dit werkt niet omdat de compiler niet begrijpt welke variant je wilt aanroepen. De tweede versie is ook geldig met een lege lijst (dit werkt enkel met variabelen, niet met types blijkbaar).
Hier zit ik dus een beetje vast. Er moeten twee variaties zijn omdat anders de compiler blijft uitrollen terwijl alle parameters op zijn (wat dus een error geeft dat er geen T meer is), maar er kunnen geen twee variaties zijn omdat de compiler dat niet slikt.
Een enkele variant zou natuurlijk leuker zijn, als je bijvoorbeeld iets als dit zou kunnen doen:
C++:
1
2
3
4
5
6
7
8
9
| /** * Constant expression to check whether all parameters * are valid constructor arguments for the requested * class. * * This does not work, as it is ambiguous, the second * version is also valid with an empty parameter pack. */ template <typename T, typename ...More> constexpr bool validParameter() { return std::is_constructible<Parameter, T>::value && sizeof...(More) > 0 ? validParameter<More...>() : true; } |
wat natuurlijk ook niet werkt omdat, ondanks dat de ternary operator er voor zorgt dat de functie niet wordt aangeroepen als er geen argumenten meer zijn, deze wel moet bestaan (en dat is natuurlijk niet zo).
Ik heb alles geprobeerd dat ik kan bedenken. Heeft iemand suggesties hoe ik dit aan de praat kan krijgen?
Ik ontken het bestaan van IE.