[cfe-dev] static if (was: Clang getting involved)

Jonathan 'Rynn' Sauer jonathan.sauer at gmx.de
Mon Mar 10 01:02:28 PDT 2014


Hello,

> The big problem with 'static if' is that it's three different features that have got muddled together. Those are:
> 
> 1) A static if, just like #if, where the condition must be a non-dependent constant expression, and the body can be arbitrary brace-balanced token soup. This basically just allows more advanced constants in #ifs.
> 
> constexpr bool has_interval_literals() { return __has_feature(interval_literals); }
> static if (has_interval_literals) {
>   static_assert( [1...4) + [4...6] == [1...6], "" );
> }
> 
> 2) A template if, that introduces a separately-instantiable template entity (and thus introduces a scope). As is usual for templates, the body must be instantiable for some template arguments (it can't be token soup).
> 
> 3) An enable if, that makes declarations visible or invisible depending on some predicate.
> 
> Of these:
> - the first isn't hugely useful, since a lot of the "interesting" compile time constants for this sort of check are preprocessor constant expressions too, so #if works,
> - the second is already possible using generic lambdas (but it's a little hacky):
>   if_(b, [&](auto){ blah; }, [&](auto){ blah2; }) // implementation left as an exercise to the reader

Maybe I'm misunderstanding something, but when I as a reader tried your exercise, I failed
miserably at first:

#include <type_traits>

template <bool Expr_>
struct StaticIf {
	template <typename Code_>
	static void apply(Code_)
	{
	}
};

template <>
struct StaticIf<true> {
	template <typename Code_>
	static void apply(Code_ code)
	{
		code(1);
	}
};

template <bool Expr_, typename Code_>
void static_if(Code_ code)
{
	return StaticIf<Expr_>::apply(code);
}


template <typename T>
void foo(T t)
{
	// Call <t.bar> if <T> is a class
	static_if<std::is_class<T>::value>([&](auto) {
		t.bar();
	});
}

struct Bar {
	void bar() {}
};


int main()
{
	foo(1);
	foo(Bar{});
}


This does not compile as <t> inside the generic lambda is not a name dependent on the lambda's
template parameter and thus requires <T> to be a class with a method <bar>.

It is of course possible to make <t> artificially dependent on the lambda's template parameter,
e.g. by saying:

// Make T dependent on D
template <typename T, typename D>
T makeDependent(T t, D /*d*/)
{
	return t;
}

template <typename T>
void foo(T t)
{
	// Call <t.bar> if <T> is a class
	static_if<std::is_class<T>::value>([&](auto dummy) {
		auto t2 = makeDependent(t, dummy);
		t2.bar();
	});
}


This compiles, but IMO is really hacky and inelegant. Similar to local structs are inelegant
compared to lambdas.

Do you know of a more elegant way?


Jonathan

P.S: Correct forwarding of values left as an exercise to the reader ;-)





More information about the cfe-dev mailing list