[static_if] Is there interest in a `static if` emulation library?
Hello all, With C++14 generic lambdas, it is possible to implement a sort of `static if` (see also N3613): template< typename T > void assign ( T& x, T const& y ) { x = y; static_if<boost::has_equal_to<T>::value>( std::bind([] ( auto x, auto y ) { assert(x == y); std::cout << "asserted for: " << typeid(T).name() << std::endl; }, x, y) ).else_( [] ( ) { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; } ); } The bind around the generic lambda is a bit verbose... but this code compiles, asserts when called on int, and does not assert when called on x_t (tested on clang 3.4.2): int i = 1; struct x_t { } x; assign(i, i); // asserts assign(x, x); // does not assert I have attached a reference implementation. Thank you. --Lorenzo P.S. I didn't do much background research... sorry if this topic has already been considered and discussed in Boost.
Hi Lorenzo, 2014-09-01 9:37 GMT+08:00 Lorenzo Caminiti <lorcaminiti@gmail.com>:
Hello all,
With C++14 generic lambdas, it is possible to implement a sort of `static if` (see also N3613):
template< typename T > void assign ( T& x, T const& y ) { x = y;
static_if<boost::has_equal_to<T>::value>( std::bind([] ( auto x, auto y ) { assert(x == y); std::cout << "asserted for: " << typeid(T).name() << std::endl; }, x, y) ).else_( [] ( ) { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; } ); }
The bind around the generic lambda is a bit verbose... but this code compiles, asserts when called on int, and does not assert when called on x_t (tested on clang 3.4.2):
I had similar idea, but my solution was like: ```c++ template< typename T > void assign ( T& x, T const& y ) { x = y; if_<boost::has_equal_to<T>::value> ( [=](auto...) // make it lazy { assert(x == y); std::cout << "asserted for: " << typeid(T).name() << std::endl; } , [] { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; } ); } ``` Another option is to always pass a dummy arg, and required the lambda to accept a param. e.g. `[](auto){....}`, so the verbosity of std::bind can be avoided.
Another option is to always pass a dummy arg, and required the lambda to accept a param. e.g. `[](auto){....}`, so the verbosity of std::bind can be avoided.
Its not enough for it to be lazy, since accessing non-dependent types still have to compile. That is why Lorenzo passes them in as template parameters. Another way accomplishing this is to pass in an identity function: namespace aux { struct identity { template<class T> T operator()(T&& x) const { return std::forward<T>(x); } }; template< bool Condition > struct static_if_statement { template< typename F > void then ( F const& f ) { f(identity()); } template< typename F > void else_ ( F const& ) { } }; template< > struct static_if_statement<false> { template< typename F > void then ( F const& ) { } template< typename F > void else_ ( F const& f ) { f(identity()); } }; } template< bool Condition, typename F > aux::static_if_statement<Condition> static_if ( F const& f ) { aux::static_if_statement<Condition> if_; if_.then(f); return if_; } Then you can call it like this: template< typename T > void assign ( T& x, T const& y ) { x = y; static_if<boost::has_equal_to<T>::value>([](auto f) { assert(f(x) == f(y)); std::cout << "asserted for: " << typeid(T).name() << std::endl; }) .else_([] (auto) { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; }); } The identity function will make non-dependent types dependent, so this will compile. Paul -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
2014-09-01 11:35 GMT+08:00 pfultz2 <pfultz2@yahoo.com>:
Another option is to always pass a dummy arg, and required the lambda to accept a param. e.g. `[](auto){....}`, so the verbosity of std::bind can be avoided.
Its not enough for it to be lazy, since accessing non-dependent types still have to compile. That is why Lorenzo passes them in as template parameters.
You're right! Another way accomplishing this is to pass in an identity function:
namespace aux { struct identity { template<class T> T operator()(T&& x) const { return std::forward<T>(x); } };
template< bool Condition > struct static_if_statement { template< typename F > void then ( F const& f ) { f(identity()); } template< typename F > void else_ ( F const& ) { } };
template< > struct static_if_statement<false> { template< typename F > void then ( F const& ) { } template< typename F > void else_ ( F const& f ) { f(identity()); } }; }
template< bool Condition, typename F > aux::static_if_statement<Condition> static_if ( F const& f ) { aux::static_if_statement<Condition> if_; if_.then(f); return if_; }
Then you can call it like this:
template< typename T > void assign ( T& x, T const& y ) { x = y;
static_if<boost::has_equal_to<T>::value>([](auto f) { assert(f(x) == f(y)); std::cout << "asserted for: " << typeid(T).name() << std::endl; }) .else_([] (auto) { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; }); }
The identity function will make non-dependent types dependent, so this will compile.
Good point, maybe we can call f `lazy` here.
pfultz2 wrote
Another way accomplishing this is to pass in an identity function: ... Then you can call it like this:
template< typename T > void assign ( T& x, T const& y ) { x = y;
static_if<boost::has_equal_to<T>::value>([](auto f) { assert(f(x) == f(y)); std::cout << "asserted for: " << typeid(T).name() << std::endl; }) .else_([] (auto) { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; }); }
The identity function will make non-dependent types dependent, so this will compile.
Clever! However, IMO the `auto f` would look somewhat arbitrary to users (I'd have to explain it "magically" comes from within the implementation so you don't have to the bind...). So I'd probably stick with the bind because even if more verbose it's workings are more obvious to the users. Thank you for the suggestion. --Lorenzo -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
Sorry for being dense, but why is this different than the following: lcaminiti wrote
template< typename T > void assign ( T& x, T const& y ) { x = y; if(boost::has_equal_to<T>::value){ assert(x == y); std::cout << "asserted for: " << typeid(T).name() << std::endl; } else { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; } }
since the condition is known at compile time, can't the optimizer be counted on to compile only the corresponding code? Why doesn't this give the same effect as the proposal? Robert Ramey -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
since the condition is known at compile time, can't the optimizer be counted on to compile only the corresponding code? Why doesn't this give the same effect as the proposal?
Both branches still have to compile even if the compiler determines it is dead code. Paul -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
pfultz2 wrote
since the condition is known at compile time, can't the optimizer be counted on to compile only the corresponding code? Why doesn't this give the same effect as the proposal?
Both branches still have to compile even if the compiler determines it is dead code.
Paul
So what, the final executables would be equivalent if not identical. -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On Sun, Aug 31, 2014 at 10:09 PM, Robert Ramey <ramey@rrsd.com> wrote:
pfultz2 wrote
since the condition is known at compile time, can't the optimizer be counted on to compile only the corresponding code? Why doesn't this give the same effect as the proposal?
Both branches still have to compile even if the compiler determines it is dead code.
Paul
So what, the final executables would be equivalent if not identical.
If both branches are able to compile, then yes, probably. The problem is that when you use this kind of branching, it's often the case that when the condition is false, the true branch would have a compilation failure (or vice versa). Regardless of which branch is taken, even when the condition is a compile-time constant, both branches need to be compiled and any templates involved need to be instantiated, so using a regular if either could fail compilation or greatly increase compile-times, neither of which is desirable. -- -Matt Calabrese
Matt Calabrese wrote
On Sun, Aug 31, 2014 at 10:09 PM, Robert Ramey <
ramey@
> wrote:
pfultz2 wrote
since the condition is known at compile time, can't the optimizer be counted on to compile only the corresponding code? Why doesn't this give the same effect as the proposal?
Both branches still have to compile even if the compiler determines it is dead code.
Paul
So what, the final executables would be equivalent if not identical.
If both branches are able to compile, then yes, probably. The problem is that when you use this kind of branching, it's often the case that when the condition is false, the true branch would have a compilation failure (or vice versa). Regardless of which branch is taken, even when the condition is a compile-time constant, both branches need to be compiled and any templates involved need to be instantiated, so using a regular if either could fail compilation or greatly increase compile-times, neither of which is desirable.
OK I see this now. I've used my method on a regular basis with good results - but it seems that I never had a case where the compile would fail on the false branch so it never came up for me. The compile-times argument doesn't impress me much though. Using a much more complex syntax or new library to shave a tiny bit of time of the compilation isn't a good trade off for me. It does raise an interesting question though. Wouldn't it be a good thing for the compiler to totally skip syntax checking for branches known to be false at compile time - perhaps as a release mode option? Robert Ramey -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On Mon, Sep 1, 2014 at 8:00 AM, Robert Ramey <ramey@rrsd.com> wrote:
The compile-times argument doesn't impress me much though. Using a much more complex syntax or new library to shave a tiny bit of
time of the compilation isn't a good trade off for me.
The problem is that it can be far from tiny in practice. Compile-times are often proportional to the amount of template instantiations you make, and so when one branch makes a lot of them (I.E. one line of code instantiates a ton of templates indirectly), it's horrible. Have you ever made a Spirit grammar that takes minutes to compile? Now imagine that each branch of an if at the top level creates a slightly different grammar or that uses it in a different way. Even if your condition is known through a compile-time constant bool, If you don't add a level of indirection to prevent both from being instantiated, either manually or by way of a higher-level facility like a static if, your compile time for that translation unit can possibly double, or worse. It does raise an interesting question though. Wouldn't it be a good thing
for the compiler to totally skip syntax checking for branches known to be false at compile time - perhaps as a release mode option?
While it would be nice to more easily write code that's more lazy about instantiation like this, I don't think it's quite so simple to add to the language quite like that, and especially not as something that changes based on a compiler option. We have one language and that language doesn't actually have a notion of "release" mode, even though your compiler might. It's also scary because in C++, whether or not a template gets instantiated can affect things unrelated and at a high level, like overload resolution in seemingly disconnected parts of code (such as if the template being instantiated has a friend function in it). That said, compilers can and do have options that make them noncompliant, so if one compiler vendor wanted to, there's nothing stopping them from creating such an option, but I would be very afraid of it. -- -Matt Calabrese
Matt Calabrese wrote
On Mon, Sep 1, 2014 at 8:00 AM, Robert Ramey <
ramey@
> wrote:
The compile-times argument doesn't impress me much though. Using a much more complex syntax or new library to shave a tiny bit of
time of the compilation isn't a good trade off for me.
The problem is that it can be far from tiny in practice. Compile-times are often proportional to the amount of template instantiations you make, and so when one branch makes a lot of them (I.E. one line of code instantiates a ton of templates indirectly), it's horrible. Have you ever made a Spirit grammar that takes minutes to compile?
actually I have. But that was a very unusual case. In practice I just write the simple version and worry about when I find that it's a performance bottleneck. Actually, that's what I do with all my code. Turns out that only very infrequently (never?) do I have to go back and complicate things to address compilation times. FWIW - I'm extremely skeptical of performance claims based on speculation. It wouldn't surprise me of compilers are already skipping compilation of dead branches for which it can be easily determined that there are no compile-time side-effects. In fact since I generally have very little problem with compile times these days - in spite of writing a lot of template code - I'll bet that compiler writers already do this. Not that it matters because it's just not a problem except in contrived pathological cases. Robert Ramey -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 02 Sep 2014, at 24:06, Robert Ramey <ramey@rrsd.com> wrote:
FWIW - I'm extremely skeptical of performance claims based on speculation. It wouldn't surprise me of compilers are already skipping compilation of dead branches for which it can be easily determined that there are no compile-time side-effects. In fact since I generally have very little problem with compile times these days - in spite of writing a lot of template code - I'll bet that compiler writers already do this. Not that it matters because it's just not a problem except in contrived pathological cases.
I know for a fact that Clang doesn’t (it doesn’t even have a notion of dead branches until long after all template instantiation is done), and I would be very surprised if any other compiler does - after all, that would be non-conformant, as it would allow erroneous programs to compile. Either way, my need for static if is to conditionally have specific declarations in a class template or not, so unless the library can do this (the suggested usage won’t work in a pure declaration context), it’s no use to me. Sebastian
On Mon, Sep 1, 2014 at 3:42 PM, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
Either way, my need for static if is to conditionally have specific declarations in a class template or not, so unless the library can do this (the suggested usage won’t work in a pure declaration context), it’s no use to me.
I know... too bad enable_if cannot be used to disable these data member declarations. The closest I ever came to this is to keep the data member declarations but make them "unusable". Not quite the same, but then you can use static if to program statements only when the data members are actually usable so they compile. For example: #include "static_if.hpp" #include <boost/mpl/if.hpp> #include <boost/noncopyable.hpp> #include <functional> class unusable : boost::noncopyable {}; template< bool B > struct x { typedef typename boost::mpl::if_c<B, int, unusable>::type value_type; value_type v; // unfortunately, this can't actually be disable... so make it useless. void f ( ) { value_type w; static_if<B>( std::bind([ ] ( auto w, auto v ) { w = v; }, std::ref(w), std::ref(v)) ); } }; int main ( ) { x<true> x1; x1.f(); x<false> x0; x0.f(); return 0; } --Lorenzo
Clever! However, IMO the `auto f` would look somewhat arbitrary to users.
I agree, it needs a better name. Jamboree has suggested `lazy`, which sounds good. Of course the name is defined by the user(unless a macro was used over top of this), they could pick anything, even a `_` could be used(not that I think it is a great idea).
I know... too bad enable_if cannot be used to disable these data member declarations.
You can use enable_if for member functions, by using a default template parameter. So you can disable `f` in your example, like this: template< bool B > struct x { typedef typename boost::mpl::if_c<B, int, unusable>::type value_type; value_type v; // unfortunately, this can't actually be disable, so make it useless. template< bool InternalBool = true, class=typename std::enable_if<InternalBool && B>::type> void f ( ) { value_type w; w = v; } }; The `InternalBool` is used to make the boolean clause in the enable_if dependent, otherwise it wouldn't work. However, I don't know a way to disable member variables and types using enable_if. Perhaps there is a way, I'm not aware of yet. Paul -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
Clever! However, IMO the `auto f` would look somewhat arbitrary to users.
I agree, it needs a better name. Jamboree has suggested `lazy`, which sounds good. Of course the name is defined by the user(unless a macro was used over top of this), they could pick anything, even a `_` could be used(not that I think it is a great idea).
I know... too bad enable_if cannot be used to disable these data member declarations.
You can use enable_if for member functions, by using a default template parameter. So you can disable `f` in your example, like this: template< bool B > struct x { typedef typename boost::mpl::if_c<B, int, unusable>::type value_type; value_type v; // unfortunately, this can't actually be disable, so make it useless. template< bool InternalBool = true, class=typename std::enable_if<InternalBool && B>::type> void f ( ) { value_type w; w = v; } }; The `InternalBool` is used to make the boolean clause in the enable_if dependent, otherwise it wouldn't work. However, I don't know a way to disable member variables and types using enable_if. Perhaps there is a way, I'm not aware of yet. Paul -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 02/09/2014 05:45, pfultz2 wrote:
However, I don't know a way to disable member variables and types using enable_if. Perhaps there is a way, I'm not aware of yet.
To use SFINAE, you need to make them templates. Unfortunately, both type and variable templates require an explicit parameter lists, unlike functions where they can be deduced. I'm not even sure variable templates work in this context.
On 2014-09-02 03:27, Lorenzo Caminiti wrote:
On Mon, Sep 1, 2014 at 3:42 PM, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
Either way, my need for static if is to conditionally have specific declarations in a class template or not, so unless the library can do this (the suggested usage won’t work in a pure declaration context), it’s no use to me. I know... too bad enable_if cannot be used to disable these data member declarations.
You could use inheritance. If the condition is true, you inherit from a struct that provides the data member, otherwise you inherit from a struct that does not. Ugly, I know, but it works if inheritance is an option for you.
On 02 Sep 2014, at 8:58, Roland Bock <rbock@eudoxos.de> wrote:
On 2014-09-02 03:27, Lorenzo Caminiti wrote:
You could use inheritance. If the condition is true, you inherit from a struct that provides the data member, otherwise you inherit from a struct that does not. Ugly, I know, but it works if inheritance is an option for you.
That’s what I’m doing, but as you say, it is ugly: template <typename Policy> using proxy_base_t = std::conditional_t< has(Policy::requirements, features::raw_access), proxy_raw_access<proxy_nodot<Policy>>, proxy_nodot<Policy>>; template <typename Policy> class proxy : public proxy_detail::proxy_base_t<Policy> { … }; Sebastian
On 2014-09-02 14:08, Sebastian Redl wrote:
On 02 Sep 2014, at 8:58, Roland Bock <rbock@eudoxos.de> wrote:
On 2014-09-02 03:27, Lorenzo Caminiti wrote:
You could use inheritance. If the condition is true, you inherit from a struct that provides the data member, otherwise you inherit from a struct that does not. Ugly, I know, but it works if inheritance is an option for you. That’s what I’m doing, but as you say, it is ugly:
template <typename Policy> using proxy_base_t = std::conditional_t< has(Policy::requirements, features::raw_access), proxy_raw_access<proxy_nodot<Policy>>, proxy_nodot<Policy>>;
template <typename Policy> class proxy : public proxy_detail::proxy_base_t<Policy> { … };
Sebastian
Yep, same here :-) FYI: I might host an open session to discuss mixins at CppCon on Monday (not formally confirmed yet). At least the way I envision them, they would allow adding members without inheritance. Roland
Le 02/09/14 17:34, Roland Bock a écrit :
On 2014-09-02 14:08, Sebastian Redl wrote:
On 02 Sep 2014, at 8:58, Roland Bock <rbock@eudoxos.de> wrote:
On 2014-09-02 03:27, Lorenzo Caminiti wrote:
You could use inheritance. If the condition is true, you inherit from a struct that provides the data member, otherwise you inherit from a struct that does not. Ugly, I know, but it works if inheritance is an option for you. That’s what I’m doing, but as you say, it is ugly:
template <typename Policy> using proxy_base_t = std::conditional_t< has(Policy::requirements, features::raw_access), proxy_raw_access<proxy_nodot<Policy>>, proxy_nodot<Policy>>;
template <typename Policy> class proxy : public proxy_detail::proxy_base_t<Policy> { … };
Sebastian
Yep, same here :-) An here https://github.com/boostorg/thread/blob/develop/include/boost/thread/concurr....
Vicente
On Tue, Sep 2, 2014 at 10:13 AM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
Le 02/09/14 17:34, Roland Bock a écrit :
On 2014-09-02 14:08, Sebastian Redl wrote:
On 02 Sep 2014, at 8:58, Roland Bock <rbock@eudoxos.de> wrote:
On 2014-09-02 03:27, Lorenzo Caminiti wrote:
You could use inheritance. If the condition is true, you inherit from a struct that provides the data member, otherwise you inherit from a struct that does not. Ugly, I know, but it works if inheritance is an option for you.
That’s what I’m doing, but as you say, it is ugly:
template <typename Policy> using proxy_base_t = std::conditional_t< has(Policy::requirements, features::raw_access), proxy_raw_access<proxy_nodot<Policy>>, proxy_nodot<Policy>>;
template <typename Policy> class proxy : public proxy_detail::proxy_base_t<Policy> { … };
Sebastian
Yep, same here :-)
An here https://github.com/boostorg/thread/blob/develop/include/boost/thread/concurr....
Hello all, I was thinking about the problem of disabling data member declarations a bit more. I have a couple of questions on C++14 template variables (these are not Boost related but given I was trying to solve a problem raised by this mail thread I hope you all do not mind if I ask them here instead of say comp.lang.c++). 1. Why is not possible to use enabled_if with template variables? For example, I'd expect this declaration to be ignored by the compiler when B is 0 and the use of n<0> to fail at (b). Instead clang 3.4.2 errors at (a) saying I cannot use enable_if there when B is 0. #include <type_traits> template< bool B > typename std::enable_if<B, bool>::type n = B; // (a) int main ( ) { n<1>; n<0>; // (b) return 0; } 2. Why is not possible to use template variables for data members? For example, this does not compile on clang 3.4.2 saying the member is declared as a template (implying that is illegal). struct x { template< typename T > T m; }; I am sure this is just the way C++14 template variables are supposed to work by spec, but at first glance I do not understand the reason for these "limitations". Thanks a lot! --Lorenzo
2014-09-08 6:50 GMT+02:00 Lorenzo Caminiti <lorcaminiti@gmail.com>:
2. Why is not possible to use template variables for data members?
For example, this does not compile on clang 3.4.2 saying the member is declared as a template (implying that is illegal).
struct x { template< typename T > T m; };
I am sure this is just the way C++14 template variables are supposed to work by spec, but at first glance I do not understand the reason for these "limitations".
What would be the size of this struct?
On September 8, 2014 12:50:56 AM EDT, Lorenzo Caminiti <lorcaminiti@gmail.com> wrote:
I was thinking about the problem of disabling data member declarations a bit more. I have a couple of questions on C++14 template variables
1. Why is not possible to use enabled_if with template variables?
For example, I'd expect this declaration to be ignored by the compiler when B is 0 and the use of n<0> to fail at (b). Instead clang 3.4.2 errors at (a) saying I cannot use enable_if there when B is 0.
#include <type_traits>
template< bool B > typename std::enable_if<B, bool>::type n = B; // (a)
I'm not certain why this isn't legal. I'd have to tread the Standard and don't have it handy.
2. Why is not possible to use template variables for data members?
For example, this does not compile on clang 3.4.2 saying the member is declared as a template (implying that is illegal).
struct x { template< typename T > T m; };
What size is x? It varies according to T, and T can be anything at any time according to your declaration. A member function template is self-contained. Everything about it and it's local variables are determined by its use. A class template is completely specified when instantiated. The same is not true of a data member template. ___ Rob (Sent from my portable computation engine)
On 08 Sep 2014, at 6:50, Lorenzo Caminiti <lorcaminiti@gmail.com> wrote:
Hello all,
I was thinking about the problem of disabling data member declarations a bit more. I have a couple of questions on C++14 template variables (these are not Boost related but given I was trying to solve a problem raised by this mail thread I hope you all do not mind if I ask them here instead of say comp.lang.c++).
1. Why is not possible to use enabled_if with template variables?
For example, I'd expect this declaration to be ignored by the compiler when B is 0 and the use of n<0> to fail at (b). Instead clang 3.4.2 errors at (a) saying I cannot use enable_if there when B is 0.
#include <type_traits>
template< bool B > typename std::enable_if<B, bool>::type n = B; // (a)
int main ( ) { n<1>; n<0>; // (b) return 0; }
enable_if only works in conjunction with SFINAE. Look at that line of code of yours. First, think about what this line is: n<1>; It’s not an explicit template instantiation, even though it may look like that. It’s an expression statement, containing a single declaration reference expression, referencing the variable template instantiation n<1>. This instantiates the variable template n with true for B. This means it instantiates std::enable_if<true, bool> and looks inside for the nested type “type”, which it finds to be an alias for bool. It assigns an initial value of true to that variable. The declref expression thus returns the value of that variable (initially true). Since nothing is done beyond that, the value is ignored. But let’s imagine it’s not just a reference, but, say, a function call: void launch_missiles(bool allow_inflight_abort); launch_missiles(n<1>); Now the value is taken and passed to that function. What about this? n<0>; It’s the same thing as before. Let’s make use of the variable and pass it to something (because not using a variable is unrealistic). launch_missiles(n<0>); Ok, so what does this code mean? Instantiation n with false tries to instantiate std::enable_if<false, bool> and then look for “type” inside. There’s no such thing, though. What type does n<0> have, then? So why does enable_if work in its intended use case? The reason is SFINAE - another part of C++ that is used quite differently from what it was probably intended for. SFINAE goes back a long way, but it is kind of implicit. The 1990 C++ ARM (which was the starting point for the C++ standard) described a very different way of using function templates than what we have today, but it said: “Overloading resolution for template functions and other functions of the same name is done in three steps: [1] Look for an exact match on functions; if found, call it. [2] Look for a function template from which a function that can be called with an exact match can be generated; if found, call it.” Note that the ARM description of templates is still very vague - the feature is marked “experimental”. However, the formulation “can be generated” already hints that the compiler will try to generate the necessary function from templates, but not error out completely if it encounters an error. I’m running out of time trying to trace the history of SFINAE, but I believe it was never intended to allow complicated overload resolution choices, but only to prevent accidental errors. class frobber { typedef int difference; }; class frobnicator { typedef int difference; }; // Eh, one template is good enough for all of these: template <typename Frob> Frob operator -(const Frob& f, typename Frob::difference d) { return generic code; } void foo() { 9 - 4; // 1 } Imagine that at //1, the compiler tries to instantiate all operator- templates for overload resolution, deduces int for the Frob parameter, and then chokes on trying to interpret int::difference. Simply by introducing the overload, you broke the language completely. SFINAE is really just a logical conclusion - it says that in such a case, the error shouldn’t bubble up. enable_if is just a clever way of abusing this little feature. Fundamentally, enable_if is a way of turning a compile-time boolean constant into a compile error suitable for forcing SFINAE-driven advanced overload resolution. Therefore, where there is no SFINAE, enable_if is not useful. Note that partial class template specialisation also uses SFINAE in some situations. I hope issue #2 has already been sufficiently answered by the others. Sebastian
On 08 Sep 2014, at 17:45, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
I’m running out of time trying to trace the history of SFINAE, but I believe it was never intended to allow complicated overload resolution choices, but only to prevent accidental errors.
Only read the below if you are interested in C++ history. I completed my archeology trip into the WG-21 archives and traced the history of SFINAE as well as I could. The actual wording for SFINAE came into the standard at pretty much the last second. In the December 1996 draft standard (the one sent out for national body reviews, document number N1043), there is no such thing. http://www.open-std.org/jtc1/sc22/open/n2356/ The wording suddenly appears as issue 6.64 with a resolution already in place of the document "Template Issues and Proposed Resolutions, Revision 21", in the pre-Morristown mailing. http://www.open-std.org/JTC1/SC22/WG21/docs/papers/1997/N1125.pdf And the updated draft in the same mailing, dating 29 September, already has that wording (note that open-std doesn't host this document publically): http://casteyde.christian.free.fr/cpp/cours/drafts/template.html In fact, the official document number of that draft is N1117. It appears that what we now know as SFINAE came into being between the London and Morristown meetings in non-public discussions. Someone with access to the reflector archives might be able to trace it more accurately. This strongly supports my theory that modern usage of SFINAE is as much an accident as template metaprogramming. Sebastian
Sebastian Redl wrote
On 08 Sep 2014, at 6:50, Lorenzo Caminiti <
lorcaminiti@
> wrote:
... 1. Why is not possible to use enabled_if with template variables? ... enable_if only works in conjunction with SFINAE.
Look at that line of code of yours. First, think about what this line is:
n<1>;
It’s not an explicit template instantiation, even though it may look like that. It’s an expression statement, containing a single declaration reference expression, referencing the variable template instantiation n<1>. ... I hope issue #2 has already been sufficiently answered by the others.
I understand. Thanks to you and everyone else for the explanations. It seems the best (and possibly only) way to disable data member declarations based on compile-time constants is via inheritance after all. Given that: 1. Disabling data members with a static-if library seems not possible; 2. And, based on all critiques in N3613, we will likely never have language support for static-if. I will reiterate my original question: Is there interest in a Boost.StaticIf library to do something like the following? template< typename Iter, typename Dist > void myadvance ( Iter& i, Dist n ) { Iter* p = &i; static_if<is_random_access_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { *p += n; }, p, n) ).template elif<is_bidirectional_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { if(n >= 0) while(n--) ++*p; else while(n++) --*p; }, p, n) ).template elif<is_input_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { while(n--) ++*p; }, p, n) ).else_( std::bind([ ] ( auto false_ ) { static_assert(false_, "requires InputIterator<Iter>"); }, std::false_type()) ); } Thanks again. --Lorenzo -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 18 Sep 2014, at 24:28, lcaminiti <lorcaminiti@gmail.com> wrote:
I will reiterate my original question: Is there interest in a Boost.StaticIf library to do something like the following?
I just reimplemented this for a personal project, so my answer is yes, I appear to find a use for it :-) Sebastian
Le 18/09/14 00:28, lcaminiti a écrit :
On 08 Sep 2014, at 6:50, Lorenzo Caminiti < lorcaminiti@ > wrote:
... 1. Why is not possible to use enabled_if with template variables? ... enable_if only works in conjunction with SFINAE.
Look at that line of code of yours. First, think about what this line is:
n<1>;
It’s not an explicit template instantiation, even though it may look like that. It’s an expression statement, containing a single declaration reference expression, referencing the variable template instantiation n<1>. ... I hope issue #2 has already been sufficiently answered by the others. I understand. Thanks to you and everyone else for the explanations. It seems
Sebastian Redl wrote the best (and possibly only) way to disable data member declarations based on compile-time constants is via inheritance after all.
Given that: 1. Disabling data members with a static-if library seems not possible; 2. And, based on all critiques in N3613, we will likely never have language support for static-if.
I will reiterate my original question: Is there interest in a Boost.StaticIf library to do something like the following?
template< typename Iter, typename Dist > void myadvance ( Iter& i, Dist n ) { Iter* p = &i; static_if<is_random_access_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { *p += n; }, p, n) ).template elif<is_bidirectional_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { if(n >= 0) while(n--) ++*p; else while(n++) --*p; }, p, n) ).template elif<is_input_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { while(n--) ++*p; }, p, n) ).else_( std::bind([ ] ( auto false_ ) { static_assert(false_, "requires InputIterator<Iter>"); }, std::false_type()) ); }
Yes, I think this is an interesting addition to Boost. Lorenzo, as the scope of your static_if emulation is restricted to void expressions would you mind to rename it, e.g. call_if? Thanks for you work, Vicente
Vicente Botet wrote
Le 18/09/14 00:28, lcaminiti a écrit :
I will reiterate my original question: Is there interest in a Boost.StaticIf library to do something like the following?
Yes, I think this is an interesting addition to Boost.
Lorenzo, as the scope of your static_if emulation is restricted to void expressions would you mind to rename it, e.g. call_if?
I would agree. However, "static" is still needed to indicate the if condition and its call code is evaluated at compile time, so maybe `call_static_if`... Also, I was not sure if to use `elif` (as from the preprocessor directive `#elif`) or `else_if` (as from statement `if(...) { ... } else if { ...}`). I will make a note to discuss these naming options during review, if Boost.StaticIf ever gets to that stage. Thanks. --Lorenzo -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 18 Sep 2014, at 8:07, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
Yes, I think this is an interesting addition to Boost.
Lorenzo, as the scope of your static_if emulation is restricted to void expressions would you mind to rename it, e.g. call_if?
I disagree. A normal if statement is restricted to statements, and so is this static if. Makes perfect sense to me. Given its intended usage, I don’t think “call” is appropriate here, since it’s supposed to emulate a control statement. Sebastian
Sebastian Redl wrote
On 18 Sep 2014, at 8:07, Vicente J. Botet Escriba <
vicente.botet@
> wrote:
Yes, I think this is an interesting addition to Boost.
Lorenzo, as the scope of your static_if emulation is restricted to void expressions would you mind to rename it, e.g. call_if?
I disagree. A normal if statement is restricted to statements, and so is this static if. Makes perfect sense to me. Given its intended usage, I don’t think “call” is appropriate here, since it’s supposed to emulate a control statement.
OK, noted. I will make sure this is discussed for consensus during formal review (if this library ever gets that far). Thanks. --Lorenzo -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 2014-09-01 21:27, Lorenzo Caminiti wrote:
On Mon, Sep 1, 2014 at 3:42 PM, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
Either way, my need for static if is to conditionally have specific declarations in a class template or not, so unless the library can do this (the suggested usage won’t work in a pure declaration context), it’s no use to me.
I know... too bad enable_if cannot be used to disable these data member declarations.
The closest I ever came to this is to keep the data member declarations but make them "unusable". Not quite the same, but then you can use static if to program statements only when the data members are actually usable so they compile. For example:
#include "static_if.hpp" #include <boost/mpl/if.hpp> #include <boost/noncopyable.hpp> #include <functional>
class unusable : boost::noncopyable {};
template< bool B > struct x { typedef typename boost::mpl::if_c<B, int, unusable>::type value_type; value_type v; // unfortunately, this can't actually be disable... so make it useless. <snip>
If we wanted to support this use case with a simpler syntax than inheritance then I think the smallest language change would be to permit data members of type void. Any use of such members would be ill-formed, and they would not be counted amongst the data members for the purposes of e.g. is_empty, is_standard_layout, etc. Then you could write the above code, with 'unusable' replaced by 'void'. If this misuse of void is too abhorrent, then a new keyword would of course be an option. Just a thought... someone keen could try to push this on std-proposals :). John Bytheway
On 02/09/2014 00:06, Robert Ramey wrote:
FWIW - I'm extremely skeptical of performance claims based on speculation. It wouldn't surprise me of compilers are already skipping compilation of dead branches for which it can be easily determined that there are no compile-time side-effects. In fact since I generally have very little problem with compile times these days - in spite of writing a lot of template code - I'll bet that compiler writers already do this. Not that it matters because it's just not a problem except in contrived pathological cases.
When you instantiate a template, it gets instantiated. It takes time. It takes place in RAM that is never freed until the end of processing the TU. There are costs associated to it. I don't see why there is any need to be skeptical about the workings of template mechanisms in C++ as implemented.
On Mon, Sep 1, 2014 at 11:26 AM, Matt Calabrese <rivorus@gmail.com> wrote:
On Mon, Sep 1, 2014 at 8:00 AM, Robert Ramey <ramey@rrsd.com> wrote:
The compile-times argument doesn't impress me much though. Using a much more complex syntax or new library to shave a tiny bit of
...
Even if your condition is known through a compile-time constant bool, If you don't add a level of indirection to prevent both from being instantiated, either manually or by way of a higher-level facility like a static if, your compile time for that translation unit can possibly double, or worse.
Yes, the point of static if wouldn't necessarily be performance even if compile-time performance could be improved by it, as Matt already pointed out (similar points are made in the various static if N-papers). In fact, in my original example the static if is used for correctness and not performance (so to check the assertion when T provides the appropriate operations for the check to actually compile). --Lorenzo
On 1 September 2014 10:00, Robert Ramey <ramey@rrsd.com> wrote:
Wouldn't it be a good thing for the compiler to totally skip syntax checking for branches known to be false at compile time
What exactly does that mean? Does the compiler still match braces? Parens? Quotes? IIRC, this was one of the problems with N3329 <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3329.pdf>. -- Nevin ":-)" Liber <mailto:nevin@eviloverlord.com> (847) 691-1404
here is the original example: template< typename T > void assign ( T& x, T const& y ) { x = y; if(boost::has_equal_to<T>::value){ assert(x == y); std::cout << "asserted for: " << typeid(T).name() << std::endl; } else { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; } } depending on the type of T, only one of the branches need be compiled. Whatever is done with the other branch will be thrown away so what's the point of compiling it. Of course the the C++ parser has to determine where the branches start an end, but that's way, way short of doing full compilation of the unused branch. It's been pointed out that the unused branch needs to be compiled even if the result is to be thrown away so that the program won't contain invalid code which will only create a surprise when another type for T is used. Actually, I'm quite sympathetic to this argument. Generic code shouldn't resolve to invalid code for some types. That mean that it's not really generic. But I should say that I'm not sure that the original example illustrates the utility of the proposal. It's pretty trivial in this case to see that if T has the == operator then it must be true after assignment. So if this is a serious proposal, it should come with a better motivating example. I should say that these days I use code like the above with abandon - depending upon the optimizer to drop any unused code. I don't think there's anything wrong with that. Robert Ramey -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On Tue, Sep 2, 2014 at 10:19 PM, Robert Ramey <ramey@rrsd.com> wrote:
here is the original example:
template< typename T > void assign ( T& x, T const& y ) { x = y; if(boost::has_equal_to<T>::value){ assert(x == y); std::cout << "asserted for: " << typeid(T).name() << std::endl; } else { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; } }
depending on the type of T, only one of the branches need be compiled. Whatever is done with the other branch will be thrown away so what's the point of compiling it. Of course the the C++ parser has to determine where the branches start an end, but that's way, way short of doing full compilation of the unused branch.
It's been pointed out that the unused branch needs to be compiled even if the result is to be thrown away so that the program won't contain invalid code which will only create a surprise when another type for T is used. Actually, I'm quite sympathetic to this argument. Generic code shouldn't resolve to invalid code for some types. That mean that it's not really generic.
But I should say that I'm not sure that the original example illustrates the utility of the proposal. It's pretty trivial in this case to see that if T has the == operator then it must be true after assignment. So if this is a serious proposal, it should come with a better motivating example.
I should say that these days I use code like the above with abandon - depending upon the optimizer to drop any unused code. I don't think there's anything wrong with that.
Despite your expectations, this is not how optimizers work (or are allowed to work). Would you expect the following to compile? int main() { if(1 == 2){ !@#$%^&*(); } return 0; } -kyle
Kyle Lutz wrote
On Tue, Sep 2, 2014 at 10:19 PM, Robert Ramey <
ramey@
> wrote:
It's been pointed out that the unused branch needs to be compiled even if the result is to be thrown away so that the program won't contain invalid code which will only create a surprise when another type for T is used. Actually, I'm quite sympathetic to this argument. Generic code shouldn't resolve to invalid code for some types. That mean that it's not really generic.
Despite your expectations, this is not how optimizers work (or are allowed to work). Would you expect the following to compile?
int main() { if(1 == 2){ !@#$%^&*(); } return 0; }
No - I think that's what I said above. Your example here doesn't depend upon any template parameter so I don't think it has a lot do with the discussion. In practice the compile will trap with an error and the programmer will eliminate the offending code and that will be the end of it. If the compiler just through it away, that would be the end of it as well. No real difference. Except that in this case its very clear that the programmer has some sort of intention which he hasn't been able to express - so compiling the obviously incorrect code will tell he hasn't thought enough about what he wants or he made a typographical mistake. So far I haven't seen anyone propose an example which benefits from the original proposal. Seems to me a solution to a non-existent problem. Robert Ramey -- View this message in context: http://boost.2283326.n4.nabble.com/static-if-Is-there-interest-in-a-static-i... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 03/09/2014 01:39 p.m., Robert Ramey wrote:
So far I haven't seen anyone propose an example which benefits from the original proposal. Seems to me a solution to a non-existent problem.
Purely pseudocode, but I believe the intention of the proposal is to be able to do things like: template< class InputIt, class Distance > void advance( InputIt& it, Distance n ) { static_if(is_random_access_iterator<InputIt>()) { it +=n; } static_else { /*increment/decrement it in a loop*/ } } ...without having to resort to SFINAE-d overloads nor tag dispatching, given that `it += n` is not required to compile for iterators whose category is weaker than random access. Regards, -- Agustín K-ballo Bergé.- http://talesofcpp.fusionfenix.com
On Wed, Sep 3, 2014 at 9:54 AM, Agustín K-ballo Bergé <kaballo86@hotmail.com> wrote:
On 03/09/2014 01:39 p.m., Robert Ramey wrote:
So far I haven't seen anyone propose an example which benefits from the original proposal. Seems to me a solution to a non-existent problem.
Purely pseudocode, but I believe the intention of the proposal is to be able to do things like:
template< class InputIt, class Distance > void advance( InputIt& it, Distance n ) { static_if(is_random_access_iterator<InputIt>()) { it +=n; } static_else { /*increment/decrement it in a loop*/ } }
...without having to resort to SFINAE-d overloads nor tag dispatching, given that `it += n` is not required to compile for iterators whose category is weaker than random access.
Sure, for example: template< typename Iter, typename Dist > void myadvance ( Iter& i, Dist n ) { Iter* p = &i; static_if<is_random_access_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { *p += n; }, p, n) ).template elif<is_bidirectional_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { if(n >= 0) while(n--) ++*p; else while(n++) --*p; }, p, n) ).template elif<is_input_iterator<Iter>::value>( std::bind([ ] ( auto p, auto n ) { while(n--) ++p; }, p, n) ).else_( std::bind([ ] ( auto false_ ) { static_assert(false_, "requires at least InputIterator"); }, std::false_type()) ); } Attached the full example that compiles on clang 3.4.2. --Lorenzo
On 03/09/2014 07:19, Robert Ramey wrote:
here is the original example:
template< typename T > void assign ( T& x, T const& y ) { x = y; if(boost::has_equal_to<T>::value){ assert(x == y); std::cout << "asserted for: " << typeid(T).name() << std::endl; } else { std::cout << "cannot assert for: " << typeid(T).name() << std::endl; } }
depending on the type of T, only one of the branches need be compiled. Whatever is done with the other branch will be thrown away so what's the point of compiling it. Of course the the C++ parser has to determine where the branches start an end, but that's way, way short of doing full compilation of the unused branch.
Consider the following: template<class T> void foo(T const& t) { if(has_toint<T>::value) { int a = t.toint(); std::cout << a << std::endl; } else { int a = 0; std::cout << 0 << std::endl; } } In branch 1, T must have a .toint() member, returning a value convertible to int. If T doesn't have a toint member, the code above will not compile, even though there is a branch for it. This is why static if was invented. Now: why couldn't the compiler realistically avoid to instantiate the non-taken branch automatically? To avoid inconsistencies, you would need to make sure that there are no side-effects to not instantiating that branch. That alone is a big problem, instantiating templates has side effects, that most compilers are not able to diagnose in any capacity, and any statement could instantiate any template anywhere. Even if you could, the obvious side effect would be that the code fails to compile, so you can't possibly be consistent if you choose to ignore a branch.
On 01/09/2014 17:00, Robert Ramey wrote:
It does raise an interesting question though. Wouldn't it be a good thing for the compiler to totally skip syntax checking for branches known to be false at compile time - perhaps as a release mode option?
There are rules as to how parsing, name lookup, instantiation contexts and template parameter substitution work. Your "good thing" would not be compatible with any of them.
On 2014-09-01, 11:00 AM, Robert Ramey wrote:
OK I see this now. I've used my method on a regular basis with good results - but it seems that I never had a case where the compile would fail on the false branch so it never came up for me.
Actually, I've used your method (not sure if it *is* yours, but I saw it in serialization code) for cases where it wouldn't compile on the false branch. In the linked example, there are 4 cases, but same idea as I think where you used it in serialization. The idea is something like this: template<typename T> void do_whatever_impl(boost::mpl::true_) { T::whatever(); } template<typename T> void do_whatever_impl(boost::mpl::false_) { // no T::whatever() whatever(); } template<typename T> void do_whatever() { do_whatever_impl(has_whatever<T>()); } https://bitbucket.org/cheez/dicpp/src/b78d05e976922f36959616cd2d98e55e5b1113... PS: I know I have to change to make_shared, just too lazy right now.
participants (17)
-
Agustín K-ballo Bergé
-
Andrzej Krzemienski
-
John Bytheway
-
Kyle Lutz
-
lcaminiti
-
Lorenzo Caminiti
-
Mathias Gaunard
-
Matt Calabrese
-
Nevin Liber
-
pfultz2
-
Rob Stewart
-
Robert Ramey
-
Roland Bock
-
Sebastian Redl
-
Sohail Somani
-
TONGARI J
-
Vicente J. Botet Escriba