I have problems with GCC compiling enable_if
s applied to the return value of the templated class method. With Clang, I am able to use an expression in enable_if
on the enum
template argument, while GCC refuses to compile this code.
Here is the problem description, initial code, and its subsequent modifications that try to satisfy me and compilers (unfortunately, not simultaneously).
I have a non-templated class Logic
that contains a templated class method computeThings()
which has an enum Strategy
as one of its template parameters. The logic in computeThings()
depends on the compile-time Strategy
, so if constexpr
is a reasonable way to make an implementation.
Variant 1
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
// class A and class B are dummy in this example, provided to show that there are several template
// parameters, and strategy selection effectively results in
// partial (not full) templated method specification
template <class A, class B, Strategy strategy>
int computeThings();
};
template <class A, class B, Logic::Strategy strategy>
int Logic::computeThings() {
if constexpr(strategy==strat_A)
return 0;
else
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
Variant 1 works fine and compiles both in clang and GCC. However, I want to get rid of if constexpr
and split computeThings()
into two specialized methods based on the chosen Strategy
. Reason: the function is performance-critical and contains a lot of code.
So, I am coming up with Variant 2 that uses enable_if
applied to the return value.
Variant 2
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_A,int>
computeThings();
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_B,int>
computeThings();
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_A,int>
Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<strategy==Logic::strat_B,int>
Logic::computeThings() {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
I am perfectly comfortable with variant 2 (though would appreciate feedback as well). This code compiles fine using AppleClang (and probably Clang, in general) and produces the right results. However, it fails to compile with GCC with the following error (+ the same but for the other method):
error: prototype for 'std::enable_if_t<(strategy == Logic:: strat_A),int> Logic::computeThings()' does not match any in class 'Logic' Logic::computeThings()
candidates are: template<class A, class B, Logic::Strategy strategy> std::enable_if_t<(strategy == strat_B), int> Logic::computeThings() computeThings();
candidates are: template<class A, class B, Logic::Strategy strategy> std::enable_if_t<(strategy == strat_A), int> Logic::computeThings() computeThings();
So, apparently, using a simple strategy==Logic::strat_A
conflicts with GCC. So, I came up with a solution to this that satisfies both clang and GCC, which wraps strategy==Logic::strat_A
into a struct
:
Variant 3
#include <iostream>
class Logic {
public:
enum Strategy { strat_A, strat_B };
template <Logic::Strategy strategy> struct isStratA {
static const bool value = strategy==Logic::strat_A;
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<Logic::isStratA<strategy>::value,int>
computeThings();
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<!Logic::isStratA<strategy>::value,int>
computeThings();
};
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<Logic::isStratA<strategy>::value,int>
Logic::computeThings() {
return 0;
}
template <class A, class B, Logic::Strategy strategy>
typename std::enable_if_t<!Logic::isStratA<strategy>::value,int>
Logic::computeThings() {
return 1;
}
int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}
with Variant 3, both Clang and GCC are happy. However, I am not, as I have to create a lot of dummy wrappers for an unknown reason (here, I have just one, but technically, I should have both isStratA<>
and isStratB<>
).
Questions:
- do I violate any C++ standard (or common sense) in my Variant 2?
- do I have an easy way of making a Variant 2-type solution working without going to dummy wrappers as in Variant 3?
(if that matters, GCC 7.4.0 and Apple LLVM version 10.0.0: clang-1000.11.45.5)
enable_if
which uses SFINAE to remove an overload, but there is no deduction in the examples. The successes on the second and third examples must be a compiler bug and you should not rely on this. – Taverasenable_if
if you are willing to write your template types by hand. Why wouldn't you simply do two functionscomputeThingsA
andcomputeThingsB
? It would also solve problem ofReason: the function is performance-critical and contains a lot of code.
- just call appropriate function inconstexpr if
– VitreousStrategy
enums and I am willing to trade some complications in template specialization in exchange to simpler calls in the other parts of the code. – ChapellLogic
parameterizable with a strategy. FYI: the third example will not compile with MSVC godbolt.org/z/N0NI70 – TaverasisStrat
: godbolt.org/z/fXNTWP EDIT: It passes on gcc, but still fails on MSVC as well, as @NikitaKniazev pointed – Vitreousif
condition inside it. This refactoring (var2/var3) obviously would not give any advantage over var1. – Chapellif
condition in variant 1... There's only a compile-timeif
condition. – Stratigraphyif-else
framework in Variant 1'scomputeThings()
. In theory, each instantiation of that template would simplify to a single function call, which the compiler could inline. Separate functions, no additional template magic, and the same overhead. (But not an answer as to why variant 2 sometimes works/doesn't work.) – Tris