Imperative Template Grammar

439 views
Skip to first unread message

Brian Anderle

unread,
Jul 29, 2013, 5:50:20 PM7/29/13
to std-pr...@isocpp.org
Templates are confusing.  It seems to me that there are three main divisions of C++ engineers based on template familiarity.

1. Will use templated classes supplied but will not attempt to create their own.
2. Would be comfortable implementing a generalized container or algorithm.
3. Metaprogramming: implementing type lists, expression trees, and the whole shebang.

I would wager based on personal experience that most professional C++ engineers fall into group 2.  There are various arguments as to "why", but I believe the answer is clear.

C++ engineers are taught a language that is imperative and iterative (mostly).
Template metaprogramming is declarative and recursive (mostly).

This creates a huge schism between thought patterns; one that is very difficult to overcome.  Very little of what they've been learning carries over into this new land.

Let's imagine one of these group 2 programmers wants to create a templated allocator.  For their purposes, they want this allocator to check the sizeof(T), and if that size is under some value, use a memory pool, otherwise just use the free store.  This is totally trivial to implement at runtime, but what if they wanted to do so at compile time?  Well, a naive runtime implementation might look something like this:

const unsigned MEMORY_CUTOFF = 32;

template<class T>
class MyAllocator
{
 
MyAllocator()
   
: _usePool(false)
 
{
   
if(sizeof(T) <= MEMORY_CUTOFF)
   
{
     _pool
.Initialize(/*...*/);
     _usePool
= true;
   
}
 
}


 T
* Allocate()
 
{
   
if(_usePool)
   
{
     
return _pool.Allocate();
   
}
   
else
   
{
     
return new T;
   
}
 
}


 
bool _usePool;
 
MyMemoryPool<T> _pool;
};

Of course a number of problems immediately arise, perhaps the most severe of which is the fact that between the pool path and the non-pool path, the client contract changes.  The client is responsible for freeing the memory only in the case where the pool is unused.  But I ask that for the sake of the example, you ignore these things for now.  Contrived examples are contrived.

If I wanted to convert this to a compile time decision (which I should be able to do since all the necessary information is known at compile time), I would have no idea how.  I know there must be a way, but I can't logically inch my way there like I could runtime C++ problems.  My toolset does not apply here.  I know with the super generic factorial example for metaprogramming that you see everywhere, you can specialize for specific values of T, but the factorial was actually templated from an integral type, whereas MyAllocator has a general T, and I'm testing against sizeof(T).  But even if I could specialize somehow, I would have no idea how to do that based on a const value like MEMORY_CUTOFF.

So let's see an example of the proposal proper:

template<class T>
class MyAllocator
{
 
MyAllocator()
 
{
   compile_if
(sizeof(T) <= MEMORY_CUTOFF)
   
{$
     _pool
.Initialize(...);
     _usePool
= true;
   $
}
 
}


 T
* Allocate()
 
{
   compile_if
(sizeof(T) <= MEMORY_CUTOFF)
   
{$
     
return _pool.Allocate();
   $
}
   compile_else
   
{$
     
return new T;
   $
}
 
}


 compile_if
(sizeof(T) <= MEMORY_CUTOFF)
 
{$
   
MyMemoryPool<T> _pool;
 $
}
};


Please pretend the brace-dollar-sign scopes are something much prettier that also don't require you to escape any characters within the scope.

You could also imagine that you don't want to retype out that condition each time since that's error-prone.

compile_bool USE_POOL = sizeof(T) <= MEMORY_CUTOFF

And of course that brings up a host of other questions about whether or not USE_POOL could ever be mutable and if so, what is the order of code generation over the class, etc etc, that would have to be answered, but that's outside the scope of this post.

And you could imagine having fine-tuned control over errors and warnings in your class.

compile_if(implements(T::begin()) && implements(T::end()))
{$
 
...
$
}
compile_else
{$
 compile_error
("Type " + str(T) + " does not implement both begin() and end()")
$
}

(Of course Concepts is also doing lots of good work to make this problem better as well.)

So, in a nutshell, I want macros, except they can treat T like a type instead of a token, and are run for each instantiation of the template.

Note that there's no reason this couldn't be used outside of templates, although the effect would be largely the same as macros.

It is not that this lets us to do something we could not before, but rather lets us do it in a way that mirrors the way we are used to coding and should help bridge the gap to getting more programmers, myself included, into group 3.

As an aside, I imagine this grammar would be minimalist on the same order of magnitude as something like HLSL.

Thoughts?

Nevin Liber

unread,
Jul 29, 2013, 6:01:40 PM7/29/13
to std-pr...@isocpp.org
On 29 July 2013 16:50, Brian Anderle <whatta...@gmail.com> wrote:


So let's see an example of the proposal proper:

template<class T>
class MyAllocator
{
 
MyAllocator()
 
{
   compile_if
(sizeof(T) <= MEMORY_CUTOFF)
   
{$
     _pool
.Initialize(...);
     _usePool
= true;
   $
}
 
}


 T
* Allocate()
 
{
   compile_if
(sizeof(T) <= MEMORY_CUTOFF)
   
{$
     
return _pool.Allocate();
   $
}
   compile_else
   
{$
     
return new T;
   $
}
 
}


 compile_if
(sizeof(T) <= MEMORY_CUTOFF)
 
{$
   
MyMemoryPool<T> _pool;
 $
}
};


This looks like yet another variant on static ifConcepts Lite is what the committee is looking at to address these sorts of issues.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Nicol Bolas

unread,
Jul 29, 2013, 6:27:09 PM7/29/13
to std-pr...@isocpp.org

The primary thrust of what he's talking about is to make metaprogramming work conceptually more like the rest of C++. Concepts Lite does not. It only works at the function/type level, rather than the code level. This forces you into more declarative coding patterns rather than imperative ones.

Concepts Lite solves the problem of enable_if being Godawful to look at, understand, and use. It solves the problem of making sure that types used in a template do not violate the syntactic restrictions on those types. It doesn't solve the basic problem of metaprogramming being a completely different programming paradigm from regular C++.

Granted, I'm not sure how big of a problem that is. Personally, I tend away from metaprogramming because it's so hard to do anything, not because of the conceptual programming issue (then again, I'm not exactly a stranger to declarative programming). Metaprogramming requires a lot of API cruft to do anything of worth, from enable_if to typename usage and so forth. A lot of code text is expended to do very simple (conceptually) things. Concepts Lite will help make this much simpler and easier to use, and thus I'm much more likely to use it.

Personally, I think that more C++ programmers will adopt metaprogramming techniques as they become something that requires less horrible code to write.

Nevin Liber

unread,
Jul 29, 2013, 6:43:03 PM7/29/13
to std-pr...@isocpp.org
How does his proposal differ from the static if proposals?  The reasoning against it in n3613 seems applicable here.

Ville Voutilainen

unread,
Jul 29, 2013, 6:46:28 PM7/29/13
to std-pr...@isocpp.org
If you want metaprogramming that's more imperative than declarative, look at Origin by Andrew Sutton,
and in particular
http://code.google.com/p/origin/source/browse/trunk/origin/type/traits.hpp


Martinho Fernandes

unread,
Jul 30, 2013, 6:01:03 AM7/30/13
to std-pr...@isocpp.org
On Mon, Jul 29, 2013 at 11:50 PM, Brian Anderle <whatta...@gmail.com> wrote:
>
> It is not that this lets us to do something we could not before, but rather
> lets us do it in a way that mirrors the way we are used to coding and should
> help bridge the gap to getting more programmers, myself included, into group
> 3.
>

I am used to coding in a functional way and also want the non-template
parts of the language to let me do things in a way that mirrors the
way I am used to coding.

torto...@gmail.com

unread,
Aug 8, 2013, 6:26:46 AM8/8/13
to std-pr...@isocpp.org

C++ is a multi-paradigm language. I'm not sure that template meta-programming lends itself well to being anything other than
functional. However, to my mind the biggest problem with the template programming is not that its functional but that the syntax
is horrendously ugly due to the angle brackets. You will see many cases of people describing template algorithms in
Haskell and translating them by hand. E.g. http://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/

If we could improve the syntax there it would go all way to helping people reach the original posters engineer level 3.

Nicol Bolas

unread,
Aug 8, 2013, 9:02:33 AM8/8/13
to std-pr...@isocpp.org

That doesn't invalidate his argument. C++ is an imperative language... except for metaprogramming where it suddenly becomes declarative. That's pretty schizophrenic from a language design and user perspective. There's no reason to want to make a language like that. There's just no advantage to it from the point of view of someone learning the language. Whether you're used to declarative or imperative or even both, it doesn't help the language to switch back and forth the way it does.

Indeed, thanks to features like constexpr functions and types, even metaprogramming is becoming less declarative.

Now granted, what he wants is generally just not practical. It's just a variation of `static if`, whose problems have been discussed. But these are primarily problems with language design and implement-ability, and all are problems that could have been worked out when templates were originally introduced decades ago. Concepts lite is superior primarily in that it's simpler to specify and implement, not fundamentally better from a user perspective.

It's superior from the practical need for some solution, not in the idealistic need for the best solution.

Alex B

unread,
Aug 8, 2013, 8:44:31 PM8/8/13
to std-pr...@isocpp.org
I kinda agree with most of what you say. Metaprogramming in C++ is really another language. Until it becomes more imperative, it will remain something used only by very advanced users (the few caring about metaprogramming).

I find it hard to believe that after all these years of users requesting an "evil static if" that it is still out of the radar. We all know there are all sorts of problems with an "all in" (evil) version of static if, but there surely could be a restricted static if that could satisfy most needs. Simply consider a static if with the following restrictions:
- It can only be used where a regular "if" is usually used (that is within function or block scope)
- A new nested block scope would be introduced by a static if (just like for regular if)

So with such restrictions you wouldn't be able to conditionally declare something.

That would mean:

static if (condition)
{
   
int a = 0;
}
else
{
   
string a = "";
}
a
++; // error: a is not declared in this scope (wether or not condition is met, just like for a normal if)

Really, should we always throw static if ideas to garbage? I think it is just a matter of finding the right restrictions for it.

Nicol Bolas

unread,
Aug 8, 2013, 9:28:56 PM8/8/13
to std-pr...@isocpp.org
On Thursday, August 8, 2013 5:44:31 PM UTC-7, Alex B wrote:
I kinda agree with most of what you say. Metaprogramming in C++ is really another language. Until it becomes more imperative, it will remain something used only by very advanced users (the few caring about metaprogramming).

I disagree with this. Deep metaprogramming is hard, and will likely be avoided by non-experts. However, there is a lot of "lite" metaprogramming tasks that people could do, but don't. And this is what keeps people from climbing the tree of metaprogramming: getting into even basic uses of it requires so much pain.

For example, there are times when I want the user to "derive a class from some type", but I would prefer to use a template for compile-time polymorphism rather than a virtual derived class. So the user implements against a prototype, and the class he writes could include a number of optional functions (with fallbacks handled internally).

However, there are two fundamental things that generally prevent me from doing that.

1: It is very difficult to clearly explain what the requirements are to the user within the language. If I give someone a class to derive from, it's very easy for them to see what they have to implement. They can see if implementing a virtual function is optional or not, and so forth. With templates, this simply doesn't exist.

2: Actually implementing the code for such a type is an absolute nightmare of Godawful code, particularly with regard to optional functions. std::enable_if is an absolute atrocity, used only because it works, not because it is in any way reasonable. Something as simple as checking if a type has a particular member function requires a monstrosity of code.

For an example, here's the setup work I had to do to check to see if a given template type has a member function with a certain name:

#define IF_HAS_MEM_FUNC(func, name)                                        \
   
template<typename T, typename Sign>                                 \
struct name {                                                       \
   
typedef char yes[1];                                            \
   
typedef char no [2];                                            \
   
template <typename U, U> struct type_check;                     \
   
template <typename _1> static yes &chk(type_check<Sign, &_1::func> *); \
   
template <typename   > static no  &chk(...);                    \
   
static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
}

...

IF_HAS_MEM_FUNC
(Character, has_character)

...

//Actual use:

template<typename T>
typename std::enable_if<_detail::has_character<T,
   
bool(T::*)(unsigned char, int, int)>::value, bool>::type
   
ForwardCharacter(unsigned char key, int x, int y)
{
   
return m_pApp->Character(key, x, y);
}

template<typename T>
typename _detail::activate_if<!_detail::has_character<T,
   
bool(T::*)(unsigned char, int, int)>::value, bool>::type
   
ForwardCharacter(unsigned char key, int x, int y)
{
   
return false;
}

Well, that's pretty horrible. Now, compare the version for static_if:

template<typename T> bool ForwardCharacter(unsigned char key, int x, int y)
{
 
static if(_detail::has_character<T, bool(T::*)(unsigned char, int, int)>::value)
 
{
   
return m_pApp->Character(key, x, y);
 
}
 
else
 
{
   
return false;
 
}
}

It has much of the same boilerplate, so it still has some visual syntax issues. But at least the function declaration is legible. And on the plus side, it's just one function.

And here's the version for the current concepts_lite proposal:

template<typename T>
concept bool has_character()
{
 
return requires(T t, unsigned int key, int x, int y) {{t.Character(key, x, y)} -> bool};
}

template<has_character T>
bool ForwardCharacter(unsigned char key, int x, int y)
{
   
return m_pApp->Character(key, x, y);
}

template<typename T>
bool ForwardCharacter(unsigned char key, int x, int y)
{
   
return false;
}

This requires a small bit of boilerplate, but that boilerplate far more legible than the mass of stuff that both static_if and enable_if required. An actual human being can read that code and know what it's doing, whereas the macro is something I have to look up or copy-and-paste every time I want to use it. Equally importantly, it doesn't take that much expertise to learn to write that code.

The only downside of this compared to static_if is that you have two functions again, rather than the imperative approach of one function with conditional logic. Even so, this is still superior to what we do today, and I think most C++ programmers can understand it. Sure, more complex examples may still be baffling, but the main point is that it lowers the bar for C++ programmers to try metaprogramming.

I find it hard to believe that after all these years of users requesting an "evil static if" that it is still out of the radar.

These... two years? I don't recall anyone requesting static_if until relatively recently. The focus was primarily on the full concepts proposal.

Nevin Liber

unread,
Aug 8, 2013, 9:42:02 PM8/8/13
to std-pr...@isocpp.org
On Aug 8, 2013, at 7:44 PM, Alex B <deva...@gmail.com> wrote:

static if (condition)
{
   
int a = 0;
}
else
{
   
string a = "";
}
a
++; // error: a is not declared in this scope (wether or not condition is met, just like for a normal if)

How does this differ from regular if statements?

Alex B

unread,
Aug 8, 2013, 11:03:04 PM8/8/13
to std-pr...@isocpp.org

I disagree with this.

Ok... and I still agree with you.
 
The only downside of this compared to static_if is that you have two functions again, rather than the imperative approach of one function with conditional logic. Even so, this is still superior to what we do today, and I think most C++ programmers can understand it. Sure, more complex examples may still be baffling, but the main point is that it lowers the bar for C++ programmers to try metaprogramming.

Agreed that concepts will help a lot and it solves different (but crucial) problems. It will make those kinds of overloads/specialization much easier to write and read; I am far from being against it. But for a lot of users, the simple fact of having to define several overloads is tedious.

Note that you could combine best of both and rewrite your static if example to use a concept:
static if(has_character<T>())
  ...

 

These... two years? I don't recall anyone requesting static_if until relatively recently. The focus was primarily on the full concepts proposal.

I admit it was a bit overstated. Still, ever since the D language introduced such a feature, many people wondered if it would be interesting for C++ (not talking about official proposals but general interest).

Alex B

unread,
Aug 8, 2013, 11:28:08 PM8/8/13
to std-pr...@isocpp.org
How does this differ from regular if statements?

That is part of my point: that it should pretty much be used in contexts similar to regular if statements. The only difference would be that the unchosen branches would not be compiled. So it could be nothing more than that: a "static version" of the regular if.

Still, even with those restrictions, Nicol static if example would work (but a regular if wouldn't).

Consider also the (interesting) use case implying auto return type deduction described in the following post that would work even with those restrictions:

What I suggest is that static if work for these cases where it doesn't introduce conditional declarations. But proposal N3329 goes much further than that and propose a static if that could be used almost everywhere (like at class scope) and allow it to do conditional declaration. A lot of people agree it shouldn't go that far and that is why I am proposing a restricted version.

Nevin Liber

unread,
Aug 9, 2013, 12:45:41 AM8/9/13
to std-pr...@isocpp.org
On 8 August 2013 22:28, Alex B <deva...@gmail.com> wrote:

How does this differ from regular if statements?

That is part of my point: that it should pretty much be used in contexts similar to regular if statements. The only difference would be that the unchosen branches would not be compiled.

Why doesn't normal if and optimization take care of this?  This sounds like a non-problem.
 
Still, even with those restrictions, Nicol static if example would work (but a regular if wouldn't).

But there is a fairly simple way with concepts lite to express it, even if it bothers your aesthetics. 

Consider also the (interesting) use case implying auto return type deduction described in the following post that would work even with those restrictions:

I'm pretty sure I already commented on it there. :-)
 
But proposal N3329 goes much further than that and propose a static if that could be used almost everywhere (like at class scope) and allow it to do conditional declaration. A lot of people agree it shouldn't go that far and that is why I am proposing a restricted version.

It's your time to spend writing the proposal and coming to meetings to champion it.  Keep in mind that there was strong concensus on the committee not to revisit static if until after concepts lite.

David Krauss

unread,
Aug 9, 2013, 1:26:37 AM8/9/13
to std-pr...@isocpp.org


On Friday, August 9, 2013 9:28:56 AM UTC+8, Nicol Bolas wrote:
2: Actually implementing the code for such a type is an absolute nightmare of Godawful code, particularly with regard to optional functions. std::enable_if is an absolute atrocity, used only because it works, not because it is in any way reasonable. Something as simple as checking if a type has a particular member function requires a monstrosity of code.

For an example, here's the setup work I had to do to check to see if a given template type has a member function with a certain name:

That is way too complicated, and amounts to a straw man argument. The ugly code is so because it defines a macro to handle any potential function, uses an outdated SFINAE idiom, and has a poorly-fitting interface. Here's an alternative I just threw together with little loss of generality:

#define MAKE_MEMBER_CALL_TRAIT( TRAIT_NAME, FN_NAME, ... ) \
   
template< typename t, typename converts_to = void, typename = void > \
   
struct TRAIT_NAME : std::false_type {}; /* Default specialization = invalid call expression */ \
   
\
   
template< typename t, typename converts_to > /* Constrained specialization used if expression is valid */ \
   
struct TRAIT_NAME< t, converts_to, \
       
decltype(void( static_cast< converts_to >( /* Convert to x, or by default no check due to void */ \
            std
::declval< t & >().FN_NAME( __VA_ARGS__ ) ) )) > /* Pass given arguments */ \
       
: std::true_type {};

MAKE_MEMBER_CALL_TRAIT
( fooable, foo, 1, 2, nullptr )

struct a { void foo( int, int, void * ); };

static_assert ( fooable< a >::value, "fooable" );
static_assert ( ! fooable< int >::value, "not fooable" );

But this is way too general. Your subsequent examples don't attempt to handle any overload generically. Supposing we actually want a trait, this would be a better comparison.

template< typename t, typename = void >
struct has_character : std::false_type {};

template< typename t >
struct has_character< t, decltype(void( std::declval< t & >().Character( std::uint8_t(), 0, 0 ) )) >
   
: std::true_type {};

template< typename t >
typename std::enable_if< has_character< t >::value, bool >::type
ForwardCharacter( std::uint8_t key, int a, int b ) { ... }

template< typename t >
typename std::enable_if< ! has_character< t >::value, bool >::type
ForwardCharacter( std::uint8_t key, int a, int b ) {}

There's really no need for a trait if we're just overloading this one function.

template< typename t >
// Succeed (evaluate to true and use this overload) if the call expression is valid.
typename std::enable_if< ( void( std::declval< t & >().Character( std::uint8_t(), 0, 0 ) ), true),
   
bool >::type
ForwardCharacter( std::uint8_t key, int a, int b ) { ... }

bool ForwardCharacter( ... ) { return false; }

But given that one of the functions is nearly empty and there are no dependent expressions in the other, it would be simpler to use a constant condition and let dead code elimination take care of "overloading." Indeed given the trait, there's no need for static_if in this case at all.
 
template< typename t >
bool ForwardCharacter( std::uint8_t key, int a, int b ) {
   
if ( has_character< t >::value ) return ... ;
   
else return false;
}

Perhaps educational resources are lagging, but people tend to make C++ metaprogramming harder than it is. Also, the kind of person who allows things to get too complicated (e.g., deciding to do metaprogramming in the first place) is likely to get shot in the foot while trying to make a template perform computation.

In my experience, the problem isn't the basic syntax but the multitude of ways things can fail due to the intricacy of the C++ type system. For example, sometimes lvalues map to references, sometimes not. Or, this template wants to be parameterized over templates; does any potential argument have a non-type parameter?

Redesigning the language to lower the first step, when the second step is the tall one, won't fix anything. Concepts are designed mainly to help type-checking, and looking nice is just a side effect. I've not read up on static_if, but if it helps the user defer stating their invariants (such as by concepts), by making the program easier to write intuitively, then it's just setting up a trap to spring when an unexpected type comes along.

Many languages have set out to satisfy what syntax the customer wants to see, and few have been any good as a result, because syntax examples are simple but programs are complicated. Let's work on setting up a safe framework to work with, because there's no way to defang C++ of its corner cases.

(As an incidental cautionary tale, I've had a great deal of trouble even posting this because Google Groups keeps crashing with an undefined identifier in some machine-generated, probably metaprogrammed, JavaScript.)

Nicol Bolas

unread,
Aug 9, 2013, 2:05:16 AM8/9/13
to std-pr...@isocpp.org


On Thursday, August 8, 2013 9:45:41 PM UTC-7, Nevin ":-)" Liber wrote:
On 8 August 2013 22:28, Alex B <deva...@gmail.com> wrote:

How does this differ from regular if statements?

That is part of my point: that it should pretty much be used in contexts similar to regular if statements. The only difference would be that the unchosen branches would not be compiled.

Why doesn't normal if and optimization take care of this?  This sounds like a non-problem.

Static_if actively prevents compilation of either code block until the condition can be resolved. Optimization simply removes code after it's been generated. It must still be legal code, whereas static_if allows you to put code that wouldn't be legal there.

Or to put it another way, David's final suggestion (when it's actually filled in) would fail:

template< typename t >
bool ForwardCharacter( std::uint8_t key, int a, int b ) {
 
if ( has_character< t >::value )

   
return m_pApp->Character(key, x, y);
 
else
   
return false;
}

This fails to compile because `m_pApp` (which is of type `t`) may or may not have a function called `Character`. That's the entire point of the test: if that function exists with the expected signature, I want to call it. If it doesn't, then I don't want to.

You cannot do that with a regular if.

Nevin Liber

unread,
Aug 9, 2013, 2:19:44 AM8/9/13
to std-pr...@isocpp.org
On 9 August 2013 01:05, Nicol Bolas <jmck...@gmail.com> wrote:

 It must still be legal code, whereas static_if allows you to put code that wouldn't be legal there.

As was brought up in Kona, if it is not legal code, how do you know when the code block ends?  It sure sounds like it's just another syntax for macros.

You cannot do that with a regular if.

That's the wrong question.  Given the timeframes involved (you wouldn't see static if until at least C++17, if not later), can you do it with concepts lite?

Nicol Bolas

unread,
Aug 9, 2013, 2:23:30 AM8/9/13
to std-pr...@isocpp.org
On Thursday, August 8, 2013 10:26:37 PM UTC-7, David Krauss wrote:
template< typename t >
// Succeed (evaluate to true and use this overload) if the call expression is valid.
typename std::enable_if< ( void( std::declval< t & >().Character( std::uint8_t(), 0, 0 ) ), true),
   
bool >::type
ForwardCharacter( std::uint8_t key, int a, int b ) { ... }

bool ForwardCharacter( ... ) { return false; }


That's no more legible than the way I did it.

Perhaps educational resources are lagging, but people tend to make C++ metaprogramming harder than it is. Also, the kind of person who allows things to get too complicated (e.g., deciding to do metaprogramming in the first place) is likely to get shot in the foot while trying to make a template perform computation.

Um, no. What you posted is not reasonable code that is easily digestible. It's a pile of `declval`, `decltype`, and a mishmash of other stuff that makes it difficult to know what the hell's going on. This syntax obfuscates the code so much that finding basic information about the function is made a chore. What's the function name there? Where's the actual return type?
 
Can it be done? Yes. Is it obvious to anyone who's not already used to it? Absolutely not. If you can't have an intuitive grasp on this code, then it's basically a second language artificially bolted on top of the main one. This is not easily digestible code, so don't act like it is. Educational resources aren't lagging; this is simply code that most people don't want to read or write.

In my experience, the problem isn't the basic syntax but the multitude of ways things can fail due to the intricacy of the C++ type system. For example, sometimes lvalues map to references, sometimes not. Or, this template wants to be parameterized over templates; does any potential argument have a non-type parameter?

Passing templates around as template parameters is getting into hardcore metaprogramming. I focused on simpler metaprograms because those are the kind of things that people want to do, but cannot do in a reasonable fashion now.

Nicol Bolas

unread,
Aug 9, 2013, 2:30:32 AM8/9/13
to std-pr...@isocpp.org


On Thursday, August 8, 2013 11:19:44 PM UTC-7, Nevin ":-)" Liber wrote:
On 9 August 2013 01:05, Nicol Bolas <jmck...@gmail.com> wrote:

 It must still be legal code, whereas static_if allows you to put code that wouldn't be legal there.

As was brought up in Kona, if it is not legal code, how do you know when the code block ends?  It sure sounds like it's just another syntax for macros.

That was stated in the static_if paper: you have to do some basic lexical analysis, effectively storing an arbitrary list of tokens until you've matched the curly braces.
 
You cannot do that with a regular if.

That's the wrong question.

That's... not a question.
 
 Given the timeframes involved (you wouldn't see static if until at least C++17, if not later), can you do it with concepts lite?

You seem to be missing the point of the thread. Concepts lite cannot do this; you can only create additional function overloads with it. And computation-by-function-overload is effectively a form of declarative programming, which is what the thread is saying we should avoid.

I agree about the practical issues of timescale and implement-ability. But you can't deny that this furthers the notion of metaprogramming being this entire other thing that requires a different way of solving problems, compared to doing things the more natural imperative programming way.

"Perfect is the enemy of good" is often trotted out as a way to disparage trying to find a better solution than one that merely works. But I remind you that the current horrific state of metaprogramming exists precisely because people focused on "good" rather than "perfect" (though technically, its because they focused on "working"). It is that which has caused this rift between "C++ programmers who understand metaprogramming" and "most C++ programmers".

C++ needs more "perfect" and less "good enough". A little idealism and vision never hurt.

David Krauss

unread,
Aug 9, 2013, 2:46:19 AM8/9/13
to std-pr...@isocpp.org

On Friday, August 9, 2013 2:23:30 PM UTC+8, Nicol Bolas wrote:
Um, no. What you posted is not reasonable code that is easily digestible. It's a pile of `declval`, `decltype`, and a mishmash of other stuff that makes it difficult to know what the hell's going on. This syntax obfuscates the code so much that finding basic information about the function is made a chore. What's the function name there? Where's the actual return type?

What you posted is not only undigestible, but has too many bells and whistles. It's reasonable, if you can't figure out how to discriminate on the existence of one member, to attempt to make a tool to discriminate on the existence of any member, so as not to clutter ForwardCharacter, which is the problem at hand. But in this case it's not a good factoring of the problem and complexity explodes.

Knowing how much to bite off at once, and what should or shouldn't be generalized, is where resources might be lagging/lacking.
 
Can it be done? Yes. Is it obvious to anyone who's not already used to it? Absolutely not. If you can't have an intuitive grasp on this code, then it's basically a second language artificially bolted on top of the main one. This is not easily digestible code, so don't act like it is. Educational resources aren't lagging; this is simply code that most people don't want to read or write.

Someone with the basic concept that C++ works by disabling functions where the template argument causes a syntax error, can probably figure it out given the comments I provided, or if not then with a few more comments and whitespaces. It's not pretty, but it is idiomatic and the noise can be filtered out. (Idioms besides mine are valid, but what you used is far more verbose than necessary.)

Concepts should make it prettier in any case, but static_if doesn't help because it doesn't address the core problem of encapsulating a syntax error. Even with static_if you would have to define the trait, which is 99% of the ugly.
 

In my experience, the problem isn't the basic syntax but the multitude of ways things can fail due to the intricacy of the C++ type system. For example, sometimes lvalues map to references, sometimes not. Or, this template wants to be parameterized over templates; does any potential argument have a non-type parameter?

Passing templates around as template parameters is getting into hardcore metaprogramming. I focused on simpler metaprograms because those are the kind of things that people want to do, but cannot do in a reasonable fashion now.

There are plenty of examples. The relation between value category and reference types bites plenty of newbies. The difference between specialization and inheritance from a specialized type is another.

I'm not saying metaprogramming is easy, just that it's made harder by the prevalence of outdated code bases (nobody should still be checking sizeof yes_type), and the lack of advice about when to define a trait, when to ask the user to define a trait per the traits idiom, etc.

Nevin Liber

unread,
Aug 9, 2013, 2:53:59 AM8/9/13
to std-pr...@isocpp.org
On 9 August 2013 01:30, Nicol Bolas <jmck...@gmail.com> wrote:

You seem to be missing the point of the thread. Concepts lite cannot do this; you can only create additional function overloads with it. And computation-by-function-overload is effectively a form of declarative programming, which is what the thread is saying we should avoid.

Because apparently people don't like to use declarative mechanisms like overloaded functions, virtual functions, etc.
 
I agree about the practical issues of timescale and implement-ability.

This is supposed to be a group about proposals, after all.
 
But you can't deny that this furthers the notion of metaprogramming being this entire other thing that requires a different way of solving problems, compared to doing things the more natural imperative programming way.

That's your theory.

Of course, many people think it's good C++ when 90%+ of your code uses defaulted copy/move constructors/assignment operators, defaulted destructors, etc.  But those are all declarative.

And the reason you implement things like smart pointers and other RAII types is so that you can use them declaratively.

"Perfect is the enemy of good" is often trotted out as a way to disparage trying to find a better solution than one that merely works. But I remind you that the current horrific state of metaprogramming exists precisely because people focused on "good" rather than "perfect" (though technically, its because they focused on "working"). It is that which has caused this rift between "C++ programmers who understand metaprogramming" and "most C++ programmers".

C++ needs more "perfect" and less "good enough". A little idealism and vision never hurt.

Okay.  Let's revisit static if when it is "perfect". :-)
-- 

Ville Voutilainen

unread,
Aug 9, 2013, 3:24:41 AM8/9/13
to std-pr...@isocpp.org
On 9 August 2013 09:53, Nevin Liber <ne...@eviloverlord.com> wrote:
On 9 August 2013 01:30, Nicol Bolas <jmck...@gmail.com> wrote:

You seem to be missing the point of the thread. Concepts lite cannot do this; you can only create additional function overloads with it. And computation-by-function-overload is effectively a form of declarative programming, which is what the thread is saying we should avoid.

Because apparently people don't like to use declarative mechanisms like overloaded functions, virtual functions, etc.

FWIW, "people" probably don't like being _forced_ to write multiple overloads when they can use an if statement,
if it suits the problem at hand better. That's mostly the beef with the desire of having function-local static ifs.
Concepts lite still offers just one way, which is multiple constrained overloads.


But you can't deny that this furthers the notion of metaprogramming being this entire other thing that requires a different way of solving problems, compared to doing things the more natural imperative programming way.

That's your theory.

I think there's some amounts of evidence for that theory. Decreasing the difference in the programming models
between metaprograms and programs would very likely be a good thing.

 


C++ needs more "perfect" and less "good enough". A little idealism and vision never hurt.

Okay.  Let's revisit static if when it is "perfect". :-)



Suits me, too. Perfect solutions have the slight problem that they never ship. I'll throw static if
under the bus in a heartbeat if I can get concepts lite in a short timeframe, because as imperfect
as it may be, it solves the vast majority of the current problems, and I can live with multiple
overloads, especially if the alternative would be waiting for a "perfect" solution.

Nicol Bolas

unread,
Aug 9, 2013, 3:35:34 AM8/9/13
to std-pr...@isocpp.org
On Thursday, August 8, 2013 11:46:19 PM UTC-7, David Krauss wrote:
On Friday, August 9, 2013 2:23:30 PM UTC+8, Nicol Bolas wrote:
Um, no. What you posted is not reasonable code that is easily digestible. It's a pile of `declval`, `decltype`, and a mishmash of other stuff that makes it difficult to know what the hell's going on. This syntax obfuscates the code so much that finding basic information about the function is made a chore. What's the function name there? Where's the actual return type?

What you posted is not only undigestible, but has too many bells and whistles. It's reasonable, if you can't figure out how to discriminate on the existence of one member, to attempt to make a tool to discriminate on the existence of any member, so as not to clutter ForwardCharacter, which is the problem at hand. But in this case it's not a good factoring of the problem and complexity explodes.

Knowing how much to bite off at once, and what should or shouldn't be generalized, is where resources might be lagging/lacking.

I think you're trying to read far too much into a simple example. But if you want more details, there was more than one such function in the original file. As I said, it was part of a template class that the user provides a class that is expected to implement certain interfaces. Some of those interfaces are optional, so every optional interface needed a forwarding function like that. Which meant each one needed a means to detect if the function in question was available.

So I needed a general solution that could be applied to many such functions. It also needed to be C++98/03 compatible, because not everyone has the latest compilers (the original code used a home-grown equivalent to std::enable_if).

Can it be done? Yes. Is it obvious to anyone who's not already used to it? Absolutely not. If you can't have an intuitive grasp on this code, then it's basically a second language artificially bolted on top of the main one. This is not easily digestible code, so don't act like it is. Educational resources aren't lagging; this is simply code that most people don't want to read or write.

Someone with the basic concept that C++ works by disabling functions where the template argument causes a syntax error, can probably figure it out given the comments I provided, or if not then with a few more comments and whitespaces. It's not pretty, but it is idiomatic and the noise can be filtered out. (Idioms besides mine are valid, but what you used is far more verbose than necessary.)

And that's really the big issue with this sort of metaprogramming. SFINAE is not a "basic concept" of C++. It's not something you can look up in the standard. It's a hack. It cannot be learned from first principles or just from a cursory examination of the language. It must be taught idiomatically.

That's what static if and concepts lite both have in common: they make explicit what SFINAE was doing implicitly. They turn an idiom into a language feature. Much like deriving from boost::noncopyable was an idiom, but now we have the language feature = delete which not only covers non-copyable, but handles much more. The = delete syntax is more effective at explaining the problem, guarantees a compile-time error rather than a link-time one, and provides a far more reasonable error message when it happens.

Until something stops being a hack and starts begin a real feature, you're not going to get many non-experts using it. Regular users use explicit language features; experts use hacks.

Even moreso if the library idiom in question is "not pretty". That is a large part of the reason why people don't use metaprogramming. Code needs to be legible in order to be maintainable, and metaprogramming is only legible to people who are already steeped in its lore. It's like another language with its own bizarre and very verbose idioms and lots of "noise" that must be "filtered out" before you can understand what's going on.

Why would a relatively inexperienced user of C++ bother learning something like that? It's just too much complexity, with a lot of noise and little substance to it. Improve the signal-to-noise ratio of metaprogramming, and you're far more likely to get people to start using it. Give it a real basis in the language rather than a hack, and again, you're more likely to get people using it. Remove its idiomatic nature, so rather than a half-dozen ways of using it, there's one obvious and correct way to implement it, and you will again improve the uptake of people using it.

Concepts should make it prettier in any case, but static_if doesn't help because it doesn't address the core problem of encapsulating a syntax error. Even with static_if you would have to define the trait, which is 99% of the ugly.

No; 99% of the ugly is that the enable_if syntax confuses the function signature to the point that you can barely tell it's even declaring a function.

Nicol Bolas

unread,
Aug 9, 2013, 3:43:21 AM8/9/13
to std-pr...@isocpp.org
On Friday, August 9, 2013 12:24:41 AM UTC-7, Ville Voutilainen wrote:
Suits me, too. Perfect solutions have the slight problem that they never ship. I'll throw static if
under the bus in a heartbeat if I can get concepts lite in a short timeframe, because as imperfect
as it may be, it solves the vast majority of the current problems, and I can live with multiple
overloads, especially if the alternative would be waiting for a "perfect" solution.

That's the part I don't understand. Why is this an either/or question at all? As I understand it, the people pushing for static if are not the same people pushing for concepts lite. So... why not let them develop a static if technical specification? Why should the standards committee basically so "Go away until these other guys finish"?

It's not like the networking TS is being affected by the filesystem TS. Why should a static if TS affect the concepts lite TS?

This sounds more like backroom politicking than good standardization practice. Concepts was effectively dead before the first lite proposal came out. The closest thing we had before that was a first-pass of how one might apply concepts to the standard library, but that was just a thought experiment, not a proposal. Static if seemed like it was well under way to becoming something real. And then, within 6 months of the first concepts lite proposal, static if is cast aside as something that they might get back to at some point, maybe.

What's the reason that static if would take longer to standardize in a TS than concepts lite anyway?

Ville Voutilainen

unread,
Aug 9, 2013, 3:57:27 AM8/9/13
to std-pr...@isocpp.org
On 9 August 2013 10:43, Nicol Bolas <jmck...@gmail.com> wrote:

That's the part I don't understand. Why is this an either/or question at all? As I understand it, the people pushing for static if are not the same people pushing for concepts lite. So... why not let them develop a static if technical specification? Why should the standards committee basically so "Go away until these other guys finish"?

That's not what the committee is saying. The concepts SG will not take static if work on their plate, because they don't
think it's a viable design. They can't, however, stop other people from forming a static if SG if critical mass for such
an SG appears. Thus far it hasn't appeared.
 


It's not like the networking TS is being affected by the filesystem TS. Why should a static if TS affect the concepts lite TS?

This sounds more like backroom politicking than good standardization practice. Concepts was effectively dead before the first lite proposal came out. The closest thing we had before that was a first-pass of how one might apply concepts to the standard library, but that was just a thought experiment, not a proposal. Static if seemed like it was well under way to becoming something real. And then, within 6 months of the first concepts lite proposal, static if is cast aside as something that they might get back to at some point, maybe.

I guess the concepts lite proposal was published when it was actually implemented and otherwise ready, so that it didn't
get caught up with endless debates from the get-go.


What's the reason that static if would take longer to standardize in a TS than concepts lite anyway?


I haven't heard anyone say that concepts are a bad idea, but I have heard a handful of people say exactly
that about static if. That might end up meaning that static if will take longer to standardize via any mechanism,
since it might be killed before it gets anywhere near a standard.

Alex B

unread,
Aug 9, 2013, 2:27:53 PM8/9/13
to std-pr...@isocpp.org
On Fri, Aug 9, 2013 at 3:57 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:



On 9 August 2013 10:43, Nicol Bolas <jmck...@gmail.com> wrote:

That's the part I don't understand. Why is this an either/or question at all? As I understand it, the people pushing for static if are not the same people pushing for concepts lite. So... why not let them develop a static if technical specification? Why should the standards committee basically so "Go away until these other guys finish"?

That's not what the committee is saying. The concepts SG will not take static if work on their plate, because they don't
think it's a viable design. They can't, however, stop other people from forming a static if SG if critical mass for such
an SG appears. Thus far it hasn't appeared.
 

Really, a SG just for a "function block static if" proposal?

I think the controversial parts of the N3329 proposal are mostly about conflicts with concepts. The proposal try to solve some problems that are better solved by concepts and the 'requires' keyword. (on a side note, it also seems too confusing/unnatural for people that a static if statement wouldn't introduce a new scope, even with the usage of {} brace, but this could easily be revised)

But for usages within a function block, it doesn't get in the way of concepts at all, so it makes sense like you said to not put it in the plate of the concepts SG.

But a brand new SG just for it? Why not threat it like any other proposal?

Richard Smith

unread,
Aug 9, 2013, 2:36:49 PM8/9/13
to std-pr...@isocpp.org
On Fri, Aug 9, 2013 at 11:27 AM, Alex B <deva...@gmail.com> wrote:

On Fri, Aug 9, 2013 at 3:57 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:



On 9 August 2013 10:43, Nicol Bolas <jmck...@gmail.com> wrote:

That's the part I don't understand. Why is this an either/or question at all? As I understand it, the people pushing for static if are not the same people pushing for concepts lite. So... why not let them develop a static if technical specification? Why should the standards committee basically so "Go away until these other guys finish"?

That's not what the committee is saying. The concepts SG will not take static if work on their plate, because they don't
think it's a viable design. They can't, however, stop other people from forming a static if SG if critical mass for such
an SG appears. Thus far it hasn't appeared.
 

Really, a SG just for a "function block static if" proposal?

I think the controversial parts of the N3329 proposal are mostly about conflicts with concepts.

The "controversial" parts of N3329 are that:
1) it does not introduce a new scope, and
2) the non-selected branch is completely ignored (the tokens aren't even required to be parseable)

This makes it fundamentally incompatible with the template model used by at least two major implementations.

If, instead, it introduced a new scope (as proposed in this thread) and we had a requirement that it is possible to instantiate each arm of the static if (that is, the same requirement we have for other token sequences in templates), then I believe the over-my-dead-body objections from implementors would disappear.

Alex B

unread,
Aug 9, 2013, 2:51:13 PM8/9/13
to std-pr...@isocpp.org
2) the non-selected branch is completely ignored (the tokens aren't even required to be parseable)

By "not required to be parseable", do you refer to this (section 2.1 of the proposal):
"If the constant expression evaluates to zero, the guarded code is tokenized and ensured to be brace-balanced, but otherwise not analyzed."

Ville Voutilainen

unread,
Aug 9, 2013, 3:03:48 PM8/9/13
to std-pr...@isocpp.org
On 9 August 2013 21:36, Richard Smith <ric...@metafoo.co.uk> wrote:
The "controversial" parts of N3329 are that:
1) it does not introduce a new scope, and
2) the non-selected branch is completely ignored (the tokens aren't even required to be parseable)

This makes it fundamentally incompatible with the template model used by at least two major implementations.

If, instead, it introduced a new scope (as proposed in this thread) and we had a requirement that it is possible to instantiate each arm of the static if (that is, the same requirement we have for other token sequences in templates), then I believe the over-my-dead-body objections from implementors would disappear.




I may misunderstand what the instantiation of both branches really means, but it gives me the impression
that it makes static if completely useless. The whole point of having a static if is being able to write
code where the branch not taken can be ill-formed.

Richard Smith

unread,
Aug 9, 2013, 4:44:22 PM8/9/13
to std-pr...@isocpp.org
Sorry, my explanation wasn't very clear. I mean, we would need a requirement that there exists some set of template arguments for which it is possible to instantiate each arm of the static if *in isolation*. Put another way, if we treated each arm of the static if as a separate function template, that function template must not be ill-formed.

The relevant rule for templates is N3690 [temp.res]p8: "If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required." This is what allows implementations to parse templates and build an AST from them before they are instantiated. We couldn't do that if we had to treat the body of a static if as token soup.

Ville Voutilainen

unread,
Aug 9, 2013, 5:02:12 PM8/9/13
to std-pr...@isocpp.org
On 9 August 2013 23:44, Richard Smith <ric...@metafoo.co.uk> wrote:

Sorry, my explanation wasn't very clear. I mean, we would need a requirement that there exists some set of template arguments for which it is possible to instantiate each arm of the static if *in isolation*. Put another way, if we treated each arm of the static if as a separate function template, that function template must not be ill-formed.

The relevant rule for templates is N3690 [temp.res]p8: "If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required." This is what allows implementations to parse templates and build an AST from them before they are instantiated. We couldn't do that if we had to treat the body of a static if as token soup.




Thank you very much, that's an extremely illuminating explanation! It also explains the brief remarks
about "damage to AST-based implementations" by a certain Texan professor. ;)

Is it so that building the AST requires non-dependent lookups to happen, by which it then becomes
extremely significant whether a new scope is created or not? I'm not quite up to speed on why
the scope question was so important, I didn't grasp the reasoning provided in the meeting it
was discussed.

Richard Smith

unread,
Aug 9, 2013, 6:25:55 PM8/9/13
to std-pr...@isocpp.org
The scope question is important for the same reason -- it significantly harms implementations that parse template definitions. Consider:

int n;
template<typename T> int f() {
  static if (T::condition)
    typedef int X;
  else
    extern void X(int);
  X(n); // #1
  return n; // #2
}

What is line #1? Is it a declaration of a variable 'n' (if the condition is true), or is it a call to the function 'X' (if the condition is false)?
What is 'n' in line #2? Is it the local variable declared on the previous line, or the global variable?

It would require some serious implementation heroics to handle these cases under the early-parsed templates model.

More abstractly, here's another excerpt from [temp.res]p8: "Knowing which names are type names allows the syntax of every template to be checked." As seen above, that valuable property doesn't hold any more if 'static if' doesn't introduce a scope.

Ville Voutilainen

unread,
Aug 9, 2013, 7:13:59 PM8/9/13
to std-pr...@isocpp.org
On 10 August 2013 01:25, Richard Smith <ric...@metafoo.co.uk> wrote:

The scope question is important for the same reason -- it significantly harms implementations that parse template definitions. Consider:

int n;
template<typename T> int f() {
  static if (T::condition)
    typedef int X;
  else
    extern void X(int);
  X(n); // #1
  return n; // #2
}

What is line #1? Is it a declaration of a variable 'n' (if the condition is true), or is it a call to the function 'X' (if the condition is false)?
What is 'n' in line #2? Is it the local variable declared on the previous line, or the global variable?

It would require some serious implementation heroics to handle these cases under the early-parsed templates model.

More abstractly, here's another excerpt from [temp.res]p8: "Knowing which names are type names allows the syntax of every template to be checked." As seen above, that valuable property doesn't hold any more if 'static if' doesn't introduce a scope.




So with your example, #1 is ill-formed, as it won't find anything with its lookup,  and #2 returns the global n.
And, to put it simply, I can do things like

void whatever()
{
    /* common block */
    static if (cond) {
    // use things in common block
    } else {
    // use things in common block
    }
}

but not

void whatever()
{
    static if (cond) {
    // whatever
    } else {
    // whatever different
    }
    /* common block */
   // nothing in the static if-else is found by lookup here, since it's a new nested scope
}

and modifying your example,

int n;
extern void X(int);

template<typename T> int f() {
  static if (T::condition)
    typedef int X;
  X(n); // #1
  return n; // #2
}

the static if is a new scope, so #1 is a call of the extern function and #2 returns the global,
regardless of what the static if desperately tried to do to shadow the global X.

Richard Smith

unread,
Aug 9, 2013, 7:20:18 PM8/9/13
to std-pr...@isocpp.org
Yes, exactly. 

David Krauss

unread,
Aug 9, 2013, 9:50:26 PM8/9/13
to std-pr...@isocpp.org


On Friday, August 9, 2013 3:35:34 PM UTC+8, Nicol Bolas wrote:
 
I think you're trying to read far too much into a simple example. But if you want more details, there was more than one such function in the original file. As I said, it was part of a template class that the user provides a class that is expected to implement certain interfaces. Some of those interfaces are optional, so every optional interface needed a forwarding function like that. Which meant each one needed a means to detect if the function in question was available.

A alternative design choice could have been to ask the user to specify which interfaces are being supplied. Maybe that would have been more reasonable than planning on implementing introspection over an open-ended set of conditions.
 
So I needed a general solution that could be applied to many such functions. It also needed to be C++98/03 compatible, because not everyone has the latest compilers (the original code used a home-grown equivalent to std::enable_if).

That helps explain why follows some legacy practices, but it remains a poor point of comparison to code based on a future standard. We should be comparing C++11 to C++14.
 
And that's really the big issue with this sort of metaprogramming. SFINAE is not a "basic concept" of C++. It's not something you can look up in the standard. It's a hack. It cannot be learned from first principles or just from a cursory examination of the language. It must be taught idiomatically.

Yes it's there, in C++11 14.8.2/8. In C++03 it was certainly underspecified, but it has been made quite concrete. What does "cursory examination" have to do with anything? Because it isn't based on anything, it is a principle which remains the basis for concepts.
 
That's what static if and concepts lite both have in common: they make explicit what SFINAE was doing implicitly. They turn an idiom into a language feature.

static_if covers what enable_if does, but that's only one case of SFINAE. Your given example trait would still need to be implemented in old-fashioned SFINAE, either using functions or partial specialization.
 
Even moreso if the library idiom in question is "not pretty". That is a large part of the reason why people don't use metaprogramming. Code needs to be legible in order to be maintainable, and metaprogramming is only legible to people who are already steeped in its lore. It's like another language with its own bizarre and very verbose idioms and lots of "noise" that must be "filtered out" before you can understand what's going on.

The maintenance tasks which need to be fulfilled result from the surprising variety of things that go wrong with templates, because C++ has so many different kinds of constraints on types and expressions. Syntax to help the user say what they want applies only if they can figure out the error message in the first place. That is why concepts are quite reasonable to prioritize.

Why would a relatively inexperienced user of C++ bother learning something like that? It's just too much complexity, with a lot of noise and little substance to it. Improve the signal-to-noise ratio of metaprogramming, and you're far more likely to get people to start using it. Give it a real basis in the language rather than a hack, and again, you're more likely to get people using it. Remove its idiomatic nature, so rather than a half-dozen ways of using it, there's one obvious and correct way to implement it, and you will again improve the uptake of people using it.

I don't want to increase the uptake until the newbies would have a higher chance of success, and fewer arcane semantic dead-ends to contend with.
 
Concepts should make it prettier in any case, but static_if doesn't help because it doesn't address the core problem of encapsulating a syntax error. Even with static_if you would have to define the trait, which is 99% of the ugly.

No; 99% of the ugly is that the enable_if syntax confuses the function signature to the point that you can barely tell it's even declaring a function.
 
Maybe you find that enormous macro beautiful, but I think you're just counting lines wrong.

Nicol Bolas

unread,
Aug 9, 2013, 10:07:55 PM8/9/13
to std-pr...@isocpp.org

But without the relaxed scoping rules, you lose a lot of the usefulness of static_if. One of the biggest advantages of static_if's scoping rules over concepts is code locality. To do any type-based conditional testing with concepts, you must introduce a function. With static_if, if you want to call a function or use a default variable if that function isn't available, it's just this simple:

template<typename T> void Func(const T &t)
{
 
static if(/*can call T::OptFunc*/)
   
bool test = t.OptFunc();
 
else
   
bool test = true;

 
//Do stuff with test.
}

It's easy to read, understand, and use.

That being said, I understand the basic issue with the proposed scoping rules. I would say that the way to go about solving it is to leave the scoping rules themselves alone. Instead, restrict the ability of static_if's to introduce names that exit the scope. Specifically, if a static if branch introduces a name that will be visible after the static_if:

1: Every possible condition must also introduce that same name.
2: Every possible condition must result in a name that is "functionally identical" as far as the following code is concerned. If the name introduced is a variable, that must be a variable of the same type in all cases (not merely convertible to or a type with a similar interface. The exact same type). If the name introduced is a typedef, that typedef must result in the same type in all cases.

I know that last part will be annoying for many uses. But the idea is to preserve the effectiveness of static_if for things that would be too tedious for concepts or needlessly break code locality. static_if is a scalpel; it should not be used for things like picking one type vs. another based on some property. We have type traits for that, and it's better to move those kinds of decisions out of a function body.

Alex B

unread,
Aug 9, 2013, 11:00:32 PM8/9/13
to std-pr...@isocpp.org
That being said, I understand the basic issue with the proposed scoping rules. I would say that the way to go about solving it is to leave the scoping rules themselves alone. Instead, restrict the ability of static_if's to introduce names that exit the scope. Specifically, if a static if branch introduces a name that will be visible after the static_if:

1: Every possible condition must also introduce that same name.
2: Every possible condition must result in a name that is "functionally identical" as far as the following code is concerned. If the name introduced is a variable, that must be a variable of the same type in all cases (not merely convertible to or a type with a similar interface. The exact same type). If the name introduced is a typedef, that typedef must result in the same type in all cases.

I know that last part will be annoying for many uses. But the idea is to preserve the effectiveness of static_if for things that would be too tedious for concepts or needlessly break code locality. static_if is a scalpel; it should not be used for things like picking one type vs. another based on some property. We have type traits for that, and it's better to move those kinds of decisions out of a function body.

What if you want to declare a variable in one branch and you want it to be destroyed when going out of branch scope (think about a resource wrapped in a RAII class)? You will have to make sure that the name is different from all the names in the other branch? It means that changing a name could change the behavior... that could become a debugging nightmare.

Maybe you can enlighten me, but I fail to see what are the real advantages of having a name "leak" out of its scope. The example you made could be the following, which wouldn't require your special rules:

template<typename T> void Func(const T &t)
{
  bool test;
  
static if(/*can call T::OptFunc*/)
    
test = t.OptFunc();
  
else

    
test = true;

  
//Do stuff with test.
}

But maybe you can come up with another example that would show the usefulness of what you are asking for?

If it is to conditionally define a type, I don't think that static if should be made for that. Apart from all the (really) good reasons Richard mentioned about the implementability of scoping rules, I also think that it would be really disturbing from a user perspective to have names leak out of scope. The goal of static if is to allow a metaprogramming style that is closer to regular non-meta programming style. So it should be as close as possible to regular if. Names leaking out of scope sounds really unnatural. If there are specific needs that remain unaddressed, it doesn't mean that they have to be addressed by static if. But I'm curious to hear what those needs are.

Nicol Bolas

unread,
Aug 9, 2013, 11:23:36 PM8/9/13
to std-pr...@isocpp.org
On Friday, August 9, 2013 8:00:32 PM UTC-7, Alex B wrote:

That being said, I understand the basic issue with the proposed scoping rules. I would say that the way to go about solving it is to leave the scoping rules themselves alone. Instead, restrict the ability of static_if's to introduce names that exit the scope. Specifically, if a static if branch introduces a name that will be visible after the static_if:

1: Every possible condition must also introduce that same name.
2: Every possible condition must result in a name that is "functionally identical" as far as the following code is concerned. If the name introduced is a variable, that must be a variable of the same type in all cases (not merely convertible to or a type with a similar interface. The exact same type). If the name introduced is a typedef, that typedef must result in the same type in all cases.

I know that last part will be annoying for many uses. But the idea is to preserve the effectiveness of static_if for things that would be too tedious for concepts or needlessly break code locality. static_if is a scalpel; it should not be used for things like picking one type vs. another based on some property. We have type traits for that, and it's better to move those kinds of decisions out of a function body.

What if you want to declare a variable in one branch and you want it to be destroyed when going out of branch scope (think about a resource wrapped in a RAII class)?

Then you add another set of braces. All names introduced at the scope of the condition are exported; if you don't want a symbol to leave the scope, you need to explicitly scope it.

You will have to make sure that the name is different from all the names in the other branch? It means that changing a name could change the behavior... that could become a debugging nightmare.

Compilers will emit errors when you do it wrong. If a name leaves the condition without a corresponding one in another condition, you get a compiler error.

Maybe you can enlighten me, but I fail to see what are the real advantages of having a name "leak" out of its scope. The example you made could be the following, which wouldn't require your special rules:

template<typename T> void Func(const T &t)
{
  bool test;
  
static if(/*can call T::OptFunc*/)
    
test = t.OptFunc();
  
else
    
test = true;

  
//Do stuff with test.
}

But maybe you can come up with another example that would show the usefulness of what you are asking for?

You don't have to default construct `test`; that's the usefulness of it. Not every type is a boolean after all. Some types are expensive to default construct, and this avoid that. It's also a lot more natural for the user to initialize variables in situ than to create a default one and then fill it in after the fact.

If it is to conditionally define a type, I don't think that static if should be made for that.

But the rules I suggested expressly forbid that. If one branch has it, then all branches must too. And it must be the same type.
 
Apart from all the (really) good reasons Richard mentioned about the implementability of scoping rules, I also think that it would be really disturbing from a user perspective to have names leak out of scope. The goal of static if is to allow a metaprogramming style that is closer to regular non-meta programming style.

No, the principle goal of static if is to make metaprogramming easier for the user to use. This involves making it more imperative, but it also involves improved code locality compared to concepts. It involves making it easier to understand what's going on.

Remember: in the world of concepts, static_if is a scalpel. It solves a very specific problem: I have a function that will be 90% the same no matter the condition. But it's going to have a few lines that are different, based on some static test. If you're putting more than about 4-5 lines in any static_if conditional, then it's probably something that deserves to be done as a function. Much like making long lambda functions is bad coding style.

Alex B

unread,
Aug 10, 2013, 12:46:11 AM8/10/13
to std-pr...@isocpp.org
What if you want to declare a variable in one branch and you want it to be destroyed when going out of branch scope (think about a resource wrapped in a RAII class)?

Then you add another set of braces. All names introduced at the scope of the condition are exported; if you don't want a symbol to leave the scope, you need to explicitly scope it.

Uh... that's exactly the kind of thing that the average user would not think about.
 
Compilers will emit errors when you do it wrong. If a name leaves the condition without a corresponding one in another condition, you get a compiler error.

You will get a compiler error if you try to use the name outside of the two static if branches. If you don't try to use it outside, no compiler error. But still potential different behavior depending on the names declared the other branch.
  
You don't have to default construct `test`; that's the usefulness of it. Not every type is a boolean after all. Some types are expensive to default construct, and this avoid that. It's also a lot more natural for the user to initialize variables in situ than to create a default one and then fill it in after the fact.

To prevent default construction, there is std::optional, although I admit I don't find it ideal since it cannot guarantee that both branches will construct the type. What you propose does indeed guarantee that.

But the rules I suggested expressly forbid that. If one branch has it, then all branches must too. And it must be the same type.

Fine.

What I don't like is the implicit nature of the rules you suggested. I would personally clearly prefer having to "mark" those names to indicate that they should leak. Such a marked name would then need to have an equivalent marked name in the other branch. Consider a __leak keyword for example (I know, the name of the keyword is bad but it is only to illustrate possible usage):
template<typename T> void Func(const T &t)
{

  
static if(/*can call T::OptFunc*/)

    __leak 
bool test = t.OptFunc();
  
else
    __leak 
bool test = true;

  
//Do stuff with test.
}

Thinking about it, the problem you are trying to solve is not a problem concerning metaprogramming alone, but regular programming as well. It happens quite often that a user wants to chose between 2 (or more) ways to construct an object depending on a runtime condition. So it seems to me that it doesn't have to be strictly related to static if. You could apply the same (ideally explicit) rules to regular if as well:
template<typename T> void Func(const T &t)
{

  
if(/*any condition*/)
    __leak 
bool test = foo();
  
else
    __leak 
bool test = true;

  
//Do stuff with test.
}

Magnus Fromreide

unread,
Aug 10, 2013, 4:34:40 AM8/10/13
to std-pr...@isocpp.org


> But the rules I suggested expressly forbid that. If one branch
> has it, then all branches must too. And it must be the same
> type.
>
I think one should allow not declaring the thing in a branch if the
scope outside the static if is left from the static if.


template<typename T> void Func(const T &t)
{
static if(/*can call T::OptFunc*/)
bool test = t.OptFunc();
else
return; // or throw something();

//Do stuff with test.
I think the __leak idea is neat but how far should it be taken?


Should __leak be allowed in the declaration?

if (__leak Foo f(/* something */)) ; else return;
// do something with f



Should __leak be allowed in expressions and then generalize expressions?

if (!(__leak Foo f(/* something */))) return;
// do something with f

but also

int f = foo() + (__leak int g = bar());
// equivalent with: int g = bar(); int f = foo() + g;




Should __leak stack?
Note that this looks horrible but I have to break out of two if layers
here, this really need to be thought about .

if (/*something*/)
__leak bool test = foo();
else if (/* something else */)
// two __leak's to get out of two if scopes
__leak __leak bool test = bar();
else
__leak __leak bool test = gaz();




Should __leak be allowed on a break or continue?

while (/*condition*/) {
while (/*other condition*/) {
__leak break; // leaves the outer loop
}
}




Should __leak work in functions? (I am against this)

void f() {
__leak bool test = foo(); // declares a file scope variable
}

/MF


inkwizyt...@gmail.com

unread,
Aug 11, 2013, 7:59:04 AM8/11/13
to std-pr...@isocpp.org


On Saturday, August 10, 2013 5:23:36 AM UTC+2, Nicol Bolas wrote:

Maybe you can enlighten me, but I fail to see what are the real advantages of having a name "leak" out of its scope. The example you made could be the following, which wouldn't require your special rules:

template<typename T> void Func(const T &t)
{
  bool test;
  
static if(/*can call T::OptFunc*/)
    
test = t.OptFunc();
  
else
    
test = true;

  
//Do stuff with test.
}

But maybe you can come up with another example that would show the usefulness of what you are asking for?

You don't have to default construct `test`; that's the usefulness of it. Not every type is a boolean after all. Some types are expensive to default construct, and this avoid that. It's also a lot more natural for the user to initialize variables in situ than to create a default one and then fill it in after the fact.
This can be easy changed to:
bool test = func_with_static_if(t);
if you cant use default constructor. You could even use lambda if you dont want pollute namespace.
 

Alex B

unread,
Aug 11, 2013, 8:42:15 AM8/11/13
to std-pr...@isocpp.org

This can be easy changed to:
bool test = func_with_static_if(t);
if you cant use default constructor. You could even use lambda if you dont want pollute namespace.

The point of static if is, in appropriate contexts, to avoid having to define several functions to perform the same task. Defining a specific function just to initialize a variable goes against that spirit.

Also, concerning lambdas, it doesn't work since they are not constexpr.

Ville Voutilainen

unread,
Aug 11, 2013, 9:46:17 AM8/11/13
to std-pr...@isocpp.org
I have submitted an NB comment for C++14 to allow lambdas to be constexpr when they can be, and I think I saw
an indication that there may be another such comment.  With that in mind, I would prefer holding the wacky
__leak ideas for a while.

vattila...@yahoo.co.uk

unread,
Aug 11, 2013, 10:24:33 AM8/11/13
to std-pr...@isocpp.org
On Saturday, 10 August 2013 04:23:36 UTC+1, Nicol Bolas wrote:
On Friday, August 9, 2013 8:00:32 PM UTC-7, Alex B wrote:
Maybe you can enlighten me, but I fail to see what are the real advantages of having a name "leak" out of its scope. The example you made could be the following, which wouldn't require your special rules:
 
template<typename T> void Func(const T &t)
{
  bool test;
  
static if(/*can call T::OptFunc*/)
    
test = t.OptFunc();
  
else
    
test = true;

  
//Do stuff with test.
}
[...]

You don't have to default construct `test`; that's the usefulness of it. Not every type is a boolean after all. Some types are expensive to default construct, and [allowing declarations to leak out of the static if scope] avoid that. It's also a lot more natural for the user to initialize variables in situ than to create a default one and then fill it in after the fact.
 
An alternative in this case could be to introduce a static version of the ternary operator (?:), i.e. statically conditional expressions:
 
template<typename T> void Func(const T &t)
{
  auto test = /*can call T::OptFunc*/ static? t.OptFunc() : true;
  //Do stuff with test.
}
 
Interestingly, you might even consider relaxing the usual rule that both sub-expressions need to evaluate to a common type, thus have the effect that the type of the whole expression (and hence the variable test above) is also conditional on the static condition.

David Krauss

unread,
Aug 11, 2013, 5:57:32 PM8/11/13
to std-pr...@isocpp.org


On Sunday, August 11, 2013 10:24:33 PM UTC+8, vattila...@yahoo.co.uk wrote:
Interestingly, you might even consider relaxing the usual rule that both sub-expressions need to evaluate to a common type, thus have the effect that the type of the whole expression (and hence the variable test above) is also conditional on the static condition.

Sounds nice in principle, but consider what it would do to e.g. the specification of std::common_type. The conditional operator is already used to perform type computation, which such a change would quietly suppress.

Richard Smith

unread,
Aug 11, 2013, 6:55:00 PM8/11/13
to std-pr...@isocpp.org
common_type would presumably continue to use "?:" rather than "static?:", so would be unchanged.
Reply all
Reply to author
Forward
0 new messages