template<class T>
constexpr const T& min( const T& a, const T& b )
{
return (b < a) ? b : a;
}
struct A
{
bool operator<(A const&) const
{
return false;
}
};
constexpr const A& minA(const A& a, const A& b)
{
return (b < a) ? b : a;
}
template<class T>
inline const T& min( const T& a, const T& b )
{
return (b < a) ? b : a;
}
A a1, a2;
constexpr const A& a = min(a1, a2); // error!
const A& a = min(a1, a2); // ok
We currently have 2 evaluation contexts in standrard C++:
- normal context
- constant context
In non-standard world, we also have language-extensions like CUDA and C++AMP that provides GPU evaluation context.Before C++11, we only had normal context in standrard. Everytime a new evaluation context is introduced into the language, it also requires us to 'mark' the functions manually in order to adopt the new context, for example:
- constant expression - `constexpr`
- CUDA - `__device__`
- C++AMP - `restrict(amp)`
- coroutine - probably `resumable` or `async`
- etc...
This is annoying, why can't the compiler treat inline functions in a context-agnostic way?
It's interesting that the template one compiles while the non-template one fails.
The way standard algorithms exploits `constexpr` thus seems 'hacky' -- it makes use of the weakness of `constexpr` that it can't check if the functions called are really `constexpr` if they depend on the template args.
I'm aware of the concerns shown here:
On Sunday, October 4, 2015 at 8:52:53 AM UTC-4, TONGARI J wrote:We currently have 2 evaluation contexts in standrard C++:
- normal context
- constant context
In non-standard world, we also have language-extensions like CUDA and C++AMP that provides GPU evaluation context.Before C++11, we only had normal context in standrard. Everytime a new evaluation context is introduced into the language, it also requires us to 'mark' the functions manually in order to adopt the new context, for example:
- constant expression - `constexpr`
- CUDA - `__device__`
- C++AMP - `restrict(amp)`
- coroutine - probably `resumable` or `async`
- etc...
This is annoying, why can't the compiler treat inline functions in a context-agnostic way?
Because each of those contexts have restrictions on what is allowed within them. `constexpr` is quite restrictive compared to runtime. I don't know what goes on in `__device__` or `restrict(amp)`, but I'd bet they are also restrictive as well.
If I write code that I intend to be able to run at constexpr time, I want the compiler to make sure that I've written it correctly. I don't want the compiler to allow me to write invalid `constexpr` code and not tell me just because all of my uses of that function happen to be evaluated at runtime.
It's interesting that the template one compiles while the non-template one fails.The way standard algorithms exploits `constexpr` thus seems 'hacky' -- it makes use of the weakness of `constexpr` that it can't check if the functions called are really `constexpr` if they depend on the template args.
It's not exploring "weakness" in `constexpr`; it's exploiting rules in template instantiation. When you write a template function, whether the operations are constexpr or not depends on the template arguments passed in. So the compiler cannot detect any errors. And more importantly, the user cannot know if they've done the right thing or not.
Therefore, the rules are different for template functions than non-template functions (no matter how much Bjarne seems to want to turn them into the same thing). In non-template code, you're saying "This function will be constexpr." In template code, you're saying, "Declare this function constexpr if all of the substituted operations are constexpr."
I'm not positive, but I'd bet that the first phase of two-phase-lookup for template constexpr will statically fail if non-dependent operations are not constexpr.
I understand what you want. I just don't know why you want it. Why do you want to shut off compile-time checking?
I'm aware of the concerns shown here:
It's interesting that you linked to the second answer, but seem to have ignored all of the (very good) points in the first answer. That argues more strongly even against you `generic` idea.
A more reasonable way is to allow inline functions to be context-agnostic so we can define `min` as:
template<class T>
inline const T& min( const T& a, const T& b )
{
return (b < a) ? b : a;
}When evaluated in constant context, the restrictions of constant-expression are automatically imposed,
On 10/4/2015 12:07 PM, Andrew Tomazos wrote:
> The problem is compile-time. Compiling a constexpr function (for
> example) is expensive. Doing it automatically for all function
> definitions would increase compile times too much.
The problem is not only compile-time, `constexpr` may affects whether
the program is well-formed. Constexpr functions are potentially
instantiated in unevaluated contexts. Have a look at CWG1581 for the
gory details, or check https://llvm.org/bugs/show_bug.cgi?id=23141 for
an actual case of `constexpr`-induced breakage.
2015-10-04 21:47 GMT+08:00 Nicol Bolas <jmck...@gmail.com>:On Sunday, October 4, 2015 at 8:52:53 AM UTC-4, TONGARI J wrote:We currently have 2 evaluation contexts in standrard C++:
I'm aware of the concerns shown here:
It's interesting that you linked to the second answer, but seem to have ignored all of the (very good) points in the first answer. That argues more strongly even against you `generic` idea.
Well, the arguments are just not promising to me. When you call a generic function, if it's not applicable to a certain context, it just fails to compile, and you'll get the error, no harm will be caused.
On Sunday, October 4, 2015 at 8:07:56 AM UTC-7, Andrew Tomazos wrote:On Sun, Oct 4, 2015 at 2:52 PM, TONGARI J <tong...@gmail.com> wrote:A more reasonable way is to allow inline functions to be context-agnostic so we can define `min` as:
template<class T>
inline const T& min( const T& a, const T& b )
{
return (b < a) ? b : a;
}When evaluated in constant context, the restrictions of constant-expression are automatically imposed,This has been suggested before. You are essentially asking that all functions that could be constexpr, be implicitly constexpr. Likewise for the other "contexts".The problem is compile-time. Compiling a constexpr function (for example) is expensive. Doing it automatically for all function definitions would increase compile times too much.You have to explicitly mark the functions you want to use in constexpr contexts, so the compiler knows it doesn't have to do the extra analysis for all the other ones.
I'm not sure I understand this point. I am not really aware of exactly how constexpr evaluation is handled in compilers, but I assume it comes down to storing some type of AST when the function is parsed, and then either evaluating directly based on that or possibly converting it to some intermediate representation, which could presumably be done in a JIT fashion when actually required.
The C++AMP specification defined a new “restrict” keyword which restricted the language features allowed in the code region and also identified the code to compile for the accelerator. However, in hindsight the “restrict” keyword has several downsides. Composing functions becomes tricky as the “restrict” keyword tends to ripple through the call hierarchy. Additionally, re-using existing code is nontrivial, even in cases where the functions are short and available in header files (e.g. std::min). HCC employs a more sophisticated mechanism so that compiler effort focuses on the parallel loops, and only generates and optimizes accelerator code where required. As described in the previous section, HCC provides several mechanisms for programmers to mark parallel regions - specifically hc::parallel_for_each and [[hc_grid_launch]]. These markers indicate that the marked functions will execute on the accelerator. Likewise, any functions called by these marked functions execute on the accelerator. Thus the compiler can identify which functions will execute on the accelerator and, in cases where the functions are defined in header files, can automatically generate correct accelerator code without the need for additional function attributes.