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;
};
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;
$}
};
compile_bool USE_POOL = sizeof(T) <= MEMORY_CUTOFF
compile_if(implements(T::begin()) && implements(T::end()))
{$
...
$}
compile_else
{$
compile_error("Type " + str(T) + " does not implement both begin() and end()")
$}
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;
$}
};
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)
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).
#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;
}
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;
}
}
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;
}
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.
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)
I disagree with this.
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.
These... two years? I don't recall anyone requesting static_if until relatively recently. The focus was primarily on the full concepts proposal.
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.How does this differ from regular if statements?
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:
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.
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 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" );
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 ) {}
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; }
template< typename t >
bool ForwardCharacter( std::uint8_t key, int a, int b ) {
if ( has_character< t >::value ) return ... ;
else return false;
}
On 8 August 2013 22:28, Alex B <deva...@gmail.com> wrote: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.How does this differ from regular if statements?Why doesn't normal if and optimization take care of this? This sounds like a non-problem.
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;
}
It must still be legal code, whereas static_if allows you to put code that wouldn't be legal there.
You cannot do that with a regular if.
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; }
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?
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?
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.
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.
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.
That's your theory.
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". :-)
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.
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?
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.
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.
2) the non-selected branch is completely ignored (the tokens aren't even required to be parseable)
The "controversial" parts of N3329 are that:1) it does not introduce a new scope, and2) 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.
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.
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;elseextern void X(int);X(n); // #1return 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.
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).
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.
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.
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.
}
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.
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.
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.
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 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.
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.
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.
bool test = func_with_static_if(t);
This can be easy changed to:
if you cant use default constructor. You could even use lambda if you dont want pollute namespace.
bool test = func_with_static_if(t);
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.
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.