Conditional Inheritance in Python
I was messing around earlier today, trying to come up with a decent default value for a class to be imported in a build system I’ve been writing for a bit now. I was using a silly if-elif-else chain, and a function to return the proper class. I then wondered if it would be possible to have a default class using conditional inheritance. In python the ternary is expressed like so:
x = value if condition else not_value
This expression can be used anywhere, even when defining classes. In the case of the build system, I was trying to set the default C/C++ compiler. This resulted in the following:
import sys
windows = sys.platform == 'win32'
macsox = sys.platform == 'darwin'
linux = 'linux' in sys.platform
class GCC(object): pass
class Clang(GCC): pass
class MSVC(object): pass
class CXX(MSVC if windows else (GCC if linux else Clang)): pass
Surprisingly (or maybe not surpirsingly), it worked without issue. Whether this is something that is actually recommended is up for debate, but now I can set a ‘default’ compiler class within the toolchain (while allowing the user to change it if they wish).
Figured this was worth sharing. However, a word of warning:
Don’t actually use this in production code
Substitution Failure Is Not An Error (It is also not a Waffle House) Part 1
With the finalization of the new C++ standard, I’ve begun tinkering with some features of the language that I haven’t really needed until now, or chose a hackier route. At the suggestion of a friend, I’ve decided to ‘esplain’ what I’ve done recently that, aside from basic #ifndef and #include statement, allows the circumvention for platform or compiler specific code without use of the C preprocessor.
But, before I actually show that off, I think some explanation of some concepts that are used to achieve this might be necessary, especially because some folks struggle with it. As such, I’m writing this as a series, with the explanation of SFINAE (the easier to type acronym of the title) in conjunction with function overloading and deduction to be the main focus of this post. To help explain the concept, we’ll be using a non-car analogy. Not because car analogies are bad (they are, in fact, the worst kind of analogies), but because the example for SFINAE involving food is a little easier to stomach.
So, first, we need to comprehend a few basic, simple concepts. Specifically, that you can overload functions, and you can overload template functions.
template <typename T>
T conjunction(T) { return T(); }
Above is a simple function with a signature of (T) -> T (this is an optional new function declaration syntax available, which is used elsewhere such as in Haskell, OCaml, F#, and even Python sort of). That is, whatever the type of the parameter it takes, will be the type of the item returned. This makes it easier for the compiler to deduce what is returned by the function.
template <typename T>
T conjunction(T) { return T(); }
auto val = conjunction(5);
Effectively, the compiler says “conjunction deduction, what’s the function?” To which it replies “Hooking up integrals and scalars and numbers”.
Now, here’s where we can get a little neat. Because functions support overloading, we can specialize the conjunction function to make the deduction require fewer assumptions.
template <typename T>
T conjunction(T) { return T(); }
template <> void conjunction(void) { }
auto x = conjunction(5);
Now, conjunction can be used in a situation where type T is actually void. We can do this with literally anything, as long as we are explicit about what we overload with the function. However, what happens when we have the following?
void conjunction(int16_t);
void conjunction(uint32_t);
conjunction(0xFFFF);
Which conjunction overload will the compiler select? In this case it selects the second, and always the second, because 0xFFFF is an int literal. However, the int16_t variant of conjunction was declared first, so why did the compiler skip it, and why did it not error out because 0xFFFF is anint literal and is not unsigned? (thereby making it an int32_t) Because of SFINAE. The compiler looked at the varying factors we gave it, and discarded the functions whose signatures did not match, or were not able to be converted.
Let’s get analogical all up in this. Suppose you wished to dine at a restaurant in your current residence. Before selecting what you think is the best place to eat, varying factors are taken into account. These can range from information you know beforehand (e.g., never setting foot inside an establishment contained within a city-block sized market/emporium) to information that might be dependent on information you may not know until you need to know it (Is this the place where that guy took a monkey hostage and sang the Russian National Anthem backwards? Or was that Denny’s?). Sometimes, a restaurant can be right out (Waffle House), or not appropriate for the occasion (an Anniversary Dinner at Waffle House), or maybe result in undefined behavior (Waffle House).
Effectively, SFINAE can be viewed as a reason to avoid an error (or in the case of the restaurant analogy, a Waffle House) based on constraints that are known before the actual visitation to the restaurant, while still having the option to attempt to visit another if it turns out that possible constraints are ineligible (Just because the W is out in the Waffle House sign doesn’t mean it stopped being a Waffle House).
With SFINAE, we can sometimes get this bizarre effect where the compiler will choose the best function available (and if we’re not careful, the wrong one), simply so that it may not error. Applied to the restaurant analogy, we can sometimes get this bizarre effect where a person will choose the best restaurant available (and if we’re not careful, the wrong one), simply so that they don’t have to step foot into a Waffle House.
Next time, we’ll talk about Waffle House partial template specialization and how it relates to SFINAE.