`sfinae` proposal… useful or unnecessary?
Clash Royale CLAN TAG#URR8PPP
I am trying to learn what SFINAE is. I believe I understand it quite well; however, I do not have a lot of practice in the subject. I mean I would find it difficult to list all the cases in which SFINAE
should or could be used, or it would be difficult for me to immediately understand what a given sample of an SFINAE code should do. However, I believe I understand the core concept well enough to use it effectively.
Nonetheless looking through examples of SFINAE usage I came to the few conclusions.
One of which is: it is extremely pointless to use a chain of std::enable_if_t
, std::void_t
, std::is_same
, etc, etc, etc, to define a single type using type = int *
. IMHO (of a "newbie" with untrained eyes) such chains make the code less (extremely less) readable.
The second conclusion was: wouldn't it be useful to have the ability to check few conditions at the same time?
So I created this alias: (well "created" is waaay too big word... I just wrote 4 lines of code...)
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
Usage examples
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
// 2) within function declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
void foo;
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
// 4) To define other SFINAE-conditions as aliases:
template <typename T>
using check_xyz = sfinae<void,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
>;
template <typename T, typename = check_xyz<T>>
class foo;
And my main concern/question is: would a professional c++ developer deem sfinae</*...*/>
(as I proposed it) useful or extremely unnecessary? Are there any changes that could be introduced to the idea?
c++ sfinae
|
show 6 more comments
I am trying to learn what SFINAE is. I believe I understand it quite well; however, I do not have a lot of practice in the subject. I mean I would find it difficult to list all the cases in which SFINAE
should or could be used, or it would be difficult for me to immediately understand what a given sample of an SFINAE code should do. However, I believe I understand the core concept well enough to use it effectively.
Nonetheless looking through examples of SFINAE usage I came to the few conclusions.
One of which is: it is extremely pointless to use a chain of std::enable_if_t
, std::void_t
, std::is_same
, etc, etc, etc, to define a single type using type = int *
. IMHO (of a "newbie" with untrained eyes) such chains make the code less (extremely less) readable.
The second conclusion was: wouldn't it be useful to have the ability to check few conditions at the same time?
So I created this alias: (well "created" is waaay too big word... I just wrote 4 lines of code...)
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
Usage examples
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
// 2) within function declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
void foo;
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
// 4) To define other SFINAE-conditions as aliases:
template <typename T>
using check_xyz = sfinae<void,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
>;
template <typename T, typename = check_xyz<T>>
class foo;
And my main concern/question is: would a professional c++ developer deem sfinae</*...*/>
(as I proposed it) useful or extremely unnecessary? Are there any changes that could be introduced to the idea?
c++ sfinae
Please turn all...
into actual code. We review only complete snippets.
– t3chb0t
Dec 19 '18 at 17:43
7
@t3chb0t, but the actual code istemplate<typename T, typename ...> using sfinae = T;
andtemplate<typename T, typename ...> using SFINAE = T;
everything else is just a usage examples... Also because of the keywords "immediate context" (which can be found in cpp draw, thesfinae
section. Usage of this aliases is limited declarations only. However... I could delete definitions (it would by erase all...
by an extension).
– cukier9a7b5
Dec 19 '18 at 17:47
Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site. Please take a look at the help center.
– Mast
Dec 19 '18 at 17:48
I know, inside templates yes, but there are many...
elsewhere and I now admit that I didn't see the// usage examples:
comment. I'd be better if it was a heading so that it's better visible... let me edit this...
– t3chb0t
Dec 19 '18 at 17:50
1
I think your quesiton is fine after all. Not much code but the rest is just examples that weren't easy to notice. I'll retract my close-vote now... @Mast?
– t3chb0t
Dec 19 '18 at 17:52
|
show 6 more comments
I am trying to learn what SFINAE is. I believe I understand it quite well; however, I do not have a lot of practice in the subject. I mean I would find it difficult to list all the cases in which SFINAE
should or could be used, or it would be difficult for me to immediately understand what a given sample of an SFINAE code should do. However, I believe I understand the core concept well enough to use it effectively.
Nonetheless looking through examples of SFINAE usage I came to the few conclusions.
One of which is: it is extremely pointless to use a chain of std::enable_if_t
, std::void_t
, std::is_same
, etc, etc, etc, to define a single type using type = int *
. IMHO (of a "newbie" with untrained eyes) such chains make the code less (extremely less) readable.
The second conclusion was: wouldn't it be useful to have the ability to check few conditions at the same time?
So I created this alias: (well "created" is waaay too big word... I just wrote 4 lines of code...)
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
Usage examples
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
// 2) within function declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
void foo;
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
// 4) To define other SFINAE-conditions as aliases:
template <typename T>
using check_xyz = sfinae<void,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
>;
template <typename T, typename = check_xyz<T>>
class foo;
And my main concern/question is: would a professional c++ developer deem sfinae</*...*/>
(as I proposed it) useful or extremely unnecessary? Are there any changes that could be introduced to the idea?
c++ sfinae
I am trying to learn what SFINAE is. I believe I understand it quite well; however, I do not have a lot of practice in the subject. I mean I would find it difficult to list all the cases in which SFINAE
should or could be used, or it would be difficult for me to immediately understand what a given sample of an SFINAE code should do. However, I believe I understand the core concept well enough to use it effectively.
Nonetheless looking through examples of SFINAE usage I came to the few conclusions.
One of which is: it is extremely pointless to use a chain of std::enable_if_t
, std::void_t
, std::is_same
, etc, etc, etc, to define a single type using type = int *
. IMHO (of a "newbie" with untrained eyes) such chains make the code less (extremely less) readable.
The second conclusion was: wouldn't it be useful to have the ability to check few conditions at the same time?
So I created this alias: (well "created" is waaay too big word... I just wrote 4 lines of code...)
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
Usage examples
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
// 2) within function declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
void foo;
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
// 4) To define other SFINAE-conditions as aliases:
template <typename T>
using check_xyz = sfinae<void,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
>;
template <typename T, typename = check_xyz<T>>
class foo;
And my main concern/question is: would a professional c++ developer deem sfinae</*...*/>
(as I proposed it) useful or extremely unnecessary? Are there any changes that could be introduced to the idea?
c++ sfinae
c++ sfinae
edited Dec 21 '18 at 18:59
asked Dec 19 '18 at 17:31
cukier9a7b5
22817
22817
Please turn all...
into actual code. We review only complete snippets.
– t3chb0t
Dec 19 '18 at 17:43
7
@t3chb0t, but the actual code istemplate<typename T, typename ...> using sfinae = T;
andtemplate<typename T, typename ...> using SFINAE = T;
everything else is just a usage examples... Also because of the keywords "immediate context" (which can be found in cpp draw, thesfinae
section. Usage of this aliases is limited declarations only. However... I could delete definitions (it would by erase all...
by an extension).
– cukier9a7b5
Dec 19 '18 at 17:47
Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site. Please take a look at the help center.
– Mast
Dec 19 '18 at 17:48
I know, inside templates yes, but there are many...
elsewhere and I now admit that I didn't see the// usage examples:
comment. I'd be better if it was a heading so that it's better visible... let me edit this...
– t3chb0t
Dec 19 '18 at 17:50
1
I think your quesiton is fine after all. Not much code but the rest is just examples that weren't easy to notice. I'll retract my close-vote now... @Mast?
– t3chb0t
Dec 19 '18 at 17:52
|
show 6 more comments
Please turn all...
into actual code. We review only complete snippets.
– t3chb0t
Dec 19 '18 at 17:43
7
@t3chb0t, but the actual code istemplate<typename T, typename ...> using sfinae = T;
andtemplate<typename T, typename ...> using SFINAE = T;
everything else is just a usage examples... Also because of the keywords "immediate context" (which can be found in cpp draw, thesfinae
section. Usage of this aliases is limited declarations only. However... I could delete definitions (it would by erase all...
by an extension).
– cukier9a7b5
Dec 19 '18 at 17:47
Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site. Please take a look at the help center.
– Mast
Dec 19 '18 at 17:48
I know, inside templates yes, but there are many...
elsewhere and I now admit that I didn't see the// usage examples:
comment. I'd be better if it was a heading so that it's better visible... let me edit this...
– t3chb0t
Dec 19 '18 at 17:50
1
I think your quesiton is fine after all. Not much code but the rest is just examples that weren't easy to notice. I'll retract my close-vote now... @Mast?
– t3chb0t
Dec 19 '18 at 17:52
Please turn all
...
into actual code. We review only complete snippets.– t3chb0t
Dec 19 '18 at 17:43
Please turn all
...
into actual code. We review only complete snippets.– t3chb0t
Dec 19 '18 at 17:43
7
7
@t3chb0t, but the actual code is
template<typename T, typename ...> using sfinae = T;
and template<typename T, typename ...> using SFINAE = T;
everything else is just a usage examples... Also because of the keywords "immediate context" (which can be found in cpp draw, the sfinae
section. Usage of this aliases is limited declarations only. However... I could delete definitions (it would by erase all ...
by an extension).– cukier9a7b5
Dec 19 '18 at 17:47
@t3chb0t, but the actual code is
template<typename T, typename ...> using sfinae = T;
and template<typename T, typename ...> using SFINAE = T;
everything else is just a usage examples... Also because of the keywords "immediate context" (which can be found in cpp draw, the sfinae
section. Usage of this aliases is limited declarations only. However... I could delete definitions (it would by erase all ...
by an extension).– cukier9a7b5
Dec 19 '18 at 17:47
Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site. Please take a look at the help center.
– Mast
Dec 19 '18 at 17:48
Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site. Please take a look at the help center.
– Mast
Dec 19 '18 at 17:48
I know, inside templates yes, but there are many
...
elsewhere and I now admit that I didn't see the // usage examples:
comment. I'd be better if it was a heading so that it's better visible... let me edit this...– t3chb0t
Dec 19 '18 at 17:50
I know, inside templates yes, but there are many
...
elsewhere and I now admit that I didn't see the // usage examples:
comment. I'd be better if it was a heading so that it's better visible... let me edit this...– t3chb0t
Dec 19 '18 at 17:50
1
1
I think your quesiton is fine after all. Not much code but the rest is just examples that weren't easy to notice. I'll retract my close-vote now... @Mast?
– t3chb0t
Dec 19 '18 at 17:52
I think your quesiton is fine after all. Not much code but the rest is just examples that weren't easy to notice. I'll retract my close-vote now... @Mast?
– t3chb0t
Dec 19 '18 at 17:52
|
show 6 more comments
3 Answers
3
active
oldest
votes
I just wrote four lines of code...
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
You should have written just two lines of code — either the first two or the last two. Not both. "There's more than one way to [spell] it" is the Perl way, not the C++ way. ;) (At least not intentionally! Not intentionally pre-C++2a! Now we're getting both std::filesystem
and std::fs
, that's a less airtight position.)
As you show, your thing can be used for
template<class T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true>
void foo();
However, this is pretty verbose; surely we'd rather write std::enable_if_t<std::is_integral_v<T> && std::is_arithmetic_v<T>>
than std::enable_if_t<std::is_integral_v<T>>, std::enable_if_t<std::is_arithmetic_v<T>>
.
So personally I have a different alias that accomplishes the same goal: bool_if_t
.
template<bool B>
using bool_if_t = std::enable_if_t<B, bool>;
template<class T,
bool_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>
> = true>
void foo();
This has the advantage that the inner expression is written using normal C++ syntax rules. We don't have to remember some arbitrary rule like "x, y
means both x
and y
must be true"; we just write x && y
. And if we want to enable this function when either x
or y
is true, we don't have to invent a new primitive; we just write bool_if_t<x || y>
. bool_if_t
is much more composable than your sfinae<Ts...>
, because bool_if_t
can exploit the existing expression syntax of C++.
Recommended viewing: "A Soupçon of SFINAE" (Arthur O'Dwyer, CppCon 2017). (Yes, that's me.)
You picked examples that don't really show off the power of sfinae
, by the way.
Consider this example:
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
You take a "value-space" boolean (is_integral_v<T>
), lift it up into "SFINAE-space" with enable_if_t
, and then apply a logical AND operation in SFINAE-space using your sfinae
alias.
template <typename T>
std::enable_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>, T
> const & foo2(T const & val);
Here I take the same "value-space" boolean, perform the logical AND in value space where we have a dedicated operator &&
for maximum expressiveness; and I lift it up into SFINAE-space after doing the AND. This is clearer and also probably more efficient in terms of compile time.
Where your version helps is when your values start in SFINAE-space!
template<class T>
sfinae<T,
decltype(std::declval<T>() + std::declval<T>()),
T*,
T&
> const & foo3(T const & val);
If we were trying to replicate this code's behavior with the standard tools, we'd say something like
template<class, class=void> struct has_plus : std::false_type ;
template<class T> struct has_plus<T, decltype(void(std::declval<T>() + std::declval<T>()))> : std::true_type ;
template<class T> inline constexpr bool has_plus_v = has_plus<T>::value;
template<class T>
std::enable_if_t<
has_plus_v<T> &&
not std::is_reference_v<T> &&
not std::is_void_v<T>, T
> const & foo4(T const & val);
That is, we start with a value in SFINAE-space (decltype(std::declval<T>() + std::declval<T>())
), lift it into value-space (has_plus_v<T>
), do the logical AND in value-space, and lift the result back into SFINAE-space.
Whereas with your foo3
, you start in SFINAE-space, do the logical AND via sfinae<...>
without leaving SFINAE-space, and then you're done. Much simpler! But harder to read, I think.
A simple way to improve readability is to pick a good name. sfinae_space_and<Ts...>
might be clearer. Can you think of a way to write sfinae_space_or<Ts...>
? How about sfinae_space_not<T>
? Does it make sense, maintainability-wise, to provide one without the others?
5
Incredible how much one can write about four (two) lines of code. Very informative ;-)
– t3chb0t
Dec 19 '18 at 18:42
What's the use ofbool_if_t = std::enable_if_t<B, bool>;
? IMO it hides the intent behind it by removing the informative "enable_if" part.
– Flamefire
Dec 20 '18 at 11:56
@Flamefire: I see your point, but OTOH, the "intent" is usually obvious from context. Significantly,bool_if_t<B>=true
is 8 characters shorter thanenable_if_t<B, bool>=true
. Perhaps most significantly, it eliminates a bikeshed point: if all you have is the low-levelenable_if_t
, then you'll see people writingenable_if_t<B, XXX>=
for many different values ofXXX
(bool, int, etc). The slightly-higher-level abstraction eliminates that needless variation. (Murphy's Law: if you don't want people fiddling with the knobs, you have to remove the knobs.)
– Quuxplusone
Dec 20 '18 at 16:28
Full disclosure:enable_if_t<B, int>=0
is 4 characters shorter thanenable_if_t<B, bool>=true
, andint_if_t<B>=0
would be 4 characters shorter thanbool_if_t<B>=true
. Why did I pickbool
?... I don't know. :P Maybe I should switch toint_if_t
as my primitive.
– Quuxplusone
Dec 20 '18 at 16:30
1
@cukier9a7b5: "I was actually trying to implementsfinae_space_not
andsfinae_space_or
... Unfortunately [I failed] ... I definitely think that, maintainability-wise, it is a must to provide at least syntax fornot
,and
,or
cases." Your thoughts parallel mine! :) I'm about 95% sure that it is impossible to write eithersfinae_not
orsfinae_or
. So, by the laws of syllogism, as you stated: you shouldn't providesfinae_and
, either.
– Quuxplusone
Dec 21 '18 at 22:23
|
show 5 more comments
A bit dated...
First of all, let me say that since C++20 much of SFINAE will be unnecessary. Concepts have SFINAE enabled by default, have much nicer syntax, and have greater reuse capabilities.
Code Review
Personally I don't see much improvement over original one. What I would prefer instead of
template <typename T, typename ... >
...
is
template <typename T, bool ... Conditionals>
... //perform folding
Even then, people sometimes have non-trivial logic like AND-ing and OR-ing the results of conditionals.
Much of the complexity lies in that conditional statements, which could be moved away by using something like template variable:
template <typename T>
constexpr inline bool supports_xxx = /*is_same, trivial, etc*/;
and then people could just use
template <typename T, typename = std::enable_if_t<supports_xxx<T>>>
...
Example
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
is better written as
template <typename T>
constexpr inline bool is_int_arithmetic_v = std::is_integral_v<T>
&& std::is_arithmetic_v<T>;
template <typename T,
typename = std::enable_if_t<is_int_arithmetic_v<T>>>
class foo;
Do note the inline
, it will help users of the code to deal with ODR issues.
typename = void_t<things...>
doesn't work if you want multiple mutually exclusive templates.bool_if_t<things...> = true
works in that case. See youtube.com/watch?v=ybaE9qlhHvw&t=37m00s .
– Quuxplusone
Dec 19 '18 at 18:41
@Quuxplusone, shouldn't specialization paired with some sort of tag dispatch be used in that case? I believe it also scales better in cases where more than 2 mutually exclusive cases are present. But yeah, my advice is not a good one to make.
– Incomputable
Dec 19 '18 at 18:43
"Shouldn't" is a strong word. ;) I agree that tag dispatch is usually more readable and should be preferred (orif constexpr
in C++17). But sometimes tag dispatch can't be used; see the video's example of a conditionallyexplicit
constructor.
– Quuxplusone
Dec 19 '18 at 18:54
2
Review review: It is "Dated" "since C++20" because it "will be unecessary". Sounds like a plot from Back to the Future IV ;) Anyways, learning SFINAE may still be worthwhile while writing library code that should be backwards compatible and/or useful within older codebases. And compilers still not to catch up a lot 'til they support fully C++20.
– phresnel
Dec 20 '18 at 9:14
1
since C++20
Still a year off! SinceConcepts
were initially proposed for C++11 C++14 and C++17 but dropped before the standard was published I am not going to hold my breath until I see them in the ratified standard.
– Martin York
Dec 20 '18 at 11:36
|
show 1 more comment
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
vs
template <typename T,
std::enable_if_t<
std::is_integral_v<T>
&& std::is_arithmetic_v<T>,
bool
> = true
class foo;
This version doesn't use your new sfinae
template, and uses fewer characters, and seems no less functional.
What more in C++20 we'll have named concepts (and failing that) requires clauses. That will make much SFINAE obsolete.
What more, there are fancier ways to do SFINAE, especially in c++20
There is this:
template<template<class...>class, class...>
struct can_apply;
which tests if a template can be applied to a list of types.
With constexpr
lambdas that can appear in template non type parameter argument calculations, we can do:
template<class F>
constexpr auto apply_test(F &&);
which returns an object that, when evaluated on some arguments, returns true_type
if you can invoke F
on it, and false_type
otherwise.
template<class T,
std::enable_if_t<
apply_test((auto a, auto b)RETURNS(a+b))( std::declval<T>(), std::declval<T>() ),
bool
> = true
>
struct foo;
here we test if a type T
can be added to itself. (I also use the somewhat ubiquitous RETURNS
macro)
Or, more cleanly:
template<class T>
auto foo( T const& lhs, T const& rhs )
requires test_apply(std::plus<T>)(lhs, rhs)
assuming sufficiently SFINAE friendly std::plus<T>
.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209992%2fsfinae-proposal-useful-or-unnecessary%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
I just wrote four lines of code...
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
You should have written just two lines of code — either the first two or the last two. Not both. "There's more than one way to [spell] it" is the Perl way, not the C++ way. ;) (At least not intentionally! Not intentionally pre-C++2a! Now we're getting both std::filesystem
and std::fs
, that's a less airtight position.)
As you show, your thing can be used for
template<class T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true>
void foo();
However, this is pretty verbose; surely we'd rather write std::enable_if_t<std::is_integral_v<T> && std::is_arithmetic_v<T>>
than std::enable_if_t<std::is_integral_v<T>>, std::enable_if_t<std::is_arithmetic_v<T>>
.
So personally I have a different alias that accomplishes the same goal: bool_if_t
.
template<bool B>
using bool_if_t = std::enable_if_t<B, bool>;
template<class T,
bool_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>
> = true>
void foo();
This has the advantage that the inner expression is written using normal C++ syntax rules. We don't have to remember some arbitrary rule like "x, y
means both x
and y
must be true"; we just write x && y
. And if we want to enable this function when either x
or y
is true, we don't have to invent a new primitive; we just write bool_if_t<x || y>
. bool_if_t
is much more composable than your sfinae<Ts...>
, because bool_if_t
can exploit the existing expression syntax of C++.
Recommended viewing: "A Soupçon of SFINAE" (Arthur O'Dwyer, CppCon 2017). (Yes, that's me.)
You picked examples that don't really show off the power of sfinae
, by the way.
Consider this example:
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
You take a "value-space" boolean (is_integral_v<T>
), lift it up into "SFINAE-space" with enable_if_t
, and then apply a logical AND operation in SFINAE-space using your sfinae
alias.
template <typename T>
std::enable_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>, T
> const & foo2(T const & val);
Here I take the same "value-space" boolean, perform the logical AND in value space where we have a dedicated operator &&
for maximum expressiveness; and I lift it up into SFINAE-space after doing the AND. This is clearer and also probably more efficient in terms of compile time.
Where your version helps is when your values start in SFINAE-space!
template<class T>
sfinae<T,
decltype(std::declval<T>() + std::declval<T>()),
T*,
T&
> const & foo3(T const & val);
If we were trying to replicate this code's behavior with the standard tools, we'd say something like
template<class, class=void> struct has_plus : std::false_type ;
template<class T> struct has_plus<T, decltype(void(std::declval<T>() + std::declval<T>()))> : std::true_type ;
template<class T> inline constexpr bool has_plus_v = has_plus<T>::value;
template<class T>
std::enable_if_t<
has_plus_v<T> &&
not std::is_reference_v<T> &&
not std::is_void_v<T>, T
> const & foo4(T const & val);
That is, we start with a value in SFINAE-space (decltype(std::declval<T>() + std::declval<T>())
), lift it into value-space (has_plus_v<T>
), do the logical AND in value-space, and lift the result back into SFINAE-space.
Whereas with your foo3
, you start in SFINAE-space, do the logical AND via sfinae<...>
without leaving SFINAE-space, and then you're done. Much simpler! But harder to read, I think.
A simple way to improve readability is to pick a good name. sfinae_space_and<Ts...>
might be clearer. Can you think of a way to write sfinae_space_or<Ts...>
? How about sfinae_space_not<T>
? Does it make sense, maintainability-wise, to provide one without the others?
5
Incredible how much one can write about four (two) lines of code. Very informative ;-)
– t3chb0t
Dec 19 '18 at 18:42
What's the use ofbool_if_t = std::enable_if_t<B, bool>;
? IMO it hides the intent behind it by removing the informative "enable_if" part.
– Flamefire
Dec 20 '18 at 11:56
@Flamefire: I see your point, but OTOH, the "intent" is usually obvious from context. Significantly,bool_if_t<B>=true
is 8 characters shorter thanenable_if_t<B, bool>=true
. Perhaps most significantly, it eliminates a bikeshed point: if all you have is the low-levelenable_if_t
, then you'll see people writingenable_if_t<B, XXX>=
for many different values ofXXX
(bool, int, etc). The slightly-higher-level abstraction eliminates that needless variation. (Murphy's Law: if you don't want people fiddling with the knobs, you have to remove the knobs.)
– Quuxplusone
Dec 20 '18 at 16:28
Full disclosure:enable_if_t<B, int>=0
is 4 characters shorter thanenable_if_t<B, bool>=true
, andint_if_t<B>=0
would be 4 characters shorter thanbool_if_t<B>=true
. Why did I pickbool
?... I don't know. :P Maybe I should switch toint_if_t
as my primitive.
– Quuxplusone
Dec 20 '18 at 16:30
1
@cukier9a7b5: "I was actually trying to implementsfinae_space_not
andsfinae_space_or
... Unfortunately [I failed] ... I definitely think that, maintainability-wise, it is a must to provide at least syntax fornot
,and
,or
cases." Your thoughts parallel mine! :) I'm about 95% sure that it is impossible to write eithersfinae_not
orsfinae_or
. So, by the laws of syllogism, as you stated: you shouldn't providesfinae_and
, either.
– Quuxplusone
Dec 21 '18 at 22:23
|
show 5 more comments
I just wrote four lines of code...
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
You should have written just two lines of code — either the first two or the last two. Not both. "There's more than one way to [spell] it" is the Perl way, not the C++ way. ;) (At least not intentionally! Not intentionally pre-C++2a! Now we're getting both std::filesystem
and std::fs
, that's a less airtight position.)
As you show, your thing can be used for
template<class T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true>
void foo();
However, this is pretty verbose; surely we'd rather write std::enable_if_t<std::is_integral_v<T> && std::is_arithmetic_v<T>>
than std::enable_if_t<std::is_integral_v<T>>, std::enable_if_t<std::is_arithmetic_v<T>>
.
So personally I have a different alias that accomplishes the same goal: bool_if_t
.
template<bool B>
using bool_if_t = std::enable_if_t<B, bool>;
template<class T,
bool_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>
> = true>
void foo();
This has the advantage that the inner expression is written using normal C++ syntax rules. We don't have to remember some arbitrary rule like "x, y
means both x
and y
must be true"; we just write x && y
. And if we want to enable this function when either x
or y
is true, we don't have to invent a new primitive; we just write bool_if_t<x || y>
. bool_if_t
is much more composable than your sfinae<Ts...>
, because bool_if_t
can exploit the existing expression syntax of C++.
Recommended viewing: "A Soupçon of SFINAE" (Arthur O'Dwyer, CppCon 2017). (Yes, that's me.)
You picked examples that don't really show off the power of sfinae
, by the way.
Consider this example:
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
You take a "value-space" boolean (is_integral_v<T>
), lift it up into "SFINAE-space" with enable_if_t
, and then apply a logical AND operation in SFINAE-space using your sfinae
alias.
template <typename T>
std::enable_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>, T
> const & foo2(T const & val);
Here I take the same "value-space" boolean, perform the logical AND in value space where we have a dedicated operator &&
for maximum expressiveness; and I lift it up into SFINAE-space after doing the AND. This is clearer and also probably more efficient in terms of compile time.
Where your version helps is when your values start in SFINAE-space!
template<class T>
sfinae<T,
decltype(std::declval<T>() + std::declval<T>()),
T*,
T&
> const & foo3(T const & val);
If we were trying to replicate this code's behavior with the standard tools, we'd say something like
template<class, class=void> struct has_plus : std::false_type ;
template<class T> struct has_plus<T, decltype(void(std::declval<T>() + std::declval<T>()))> : std::true_type ;
template<class T> inline constexpr bool has_plus_v = has_plus<T>::value;
template<class T>
std::enable_if_t<
has_plus_v<T> &&
not std::is_reference_v<T> &&
not std::is_void_v<T>, T
> const & foo4(T const & val);
That is, we start with a value in SFINAE-space (decltype(std::declval<T>() + std::declval<T>())
), lift it into value-space (has_plus_v<T>
), do the logical AND in value-space, and lift the result back into SFINAE-space.
Whereas with your foo3
, you start in SFINAE-space, do the logical AND via sfinae<...>
without leaving SFINAE-space, and then you're done. Much simpler! But harder to read, I think.
A simple way to improve readability is to pick a good name. sfinae_space_and<Ts...>
might be clearer. Can you think of a way to write sfinae_space_or<Ts...>
? How about sfinae_space_not<T>
? Does it make sense, maintainability-wise, to provide one without the others?
5
Incredible how much one can write about four (two) lines of code. Very informative ;-)
– t3chb0t
Dec 19 '18 at 18:42
What's the use ofbool_if_t = std::enable_if_t<B, bool>;
? IMO it hides the intent behind it by removing the informative "enable_if" part.
– Flamefire
Dec 20 '18 at 11:56
@Flamefire: I see your point, but OTOH, the "intent" is usually obvious from context. Significantly,bool_if_t<B>=true
is 8 characters shorter thanenable_if_t<B, bool>=true
. Perhaps most significantly, it eliminates a bikeshed point: if all you have is the low-levelenable_if_t
, then you'll see people writingenable_if_t<B, XXX>=
for many different values ofXXX
(bool, int, etc). The slightly-higher-level abstraction eliminates that needless variation. (Murphy's Law: if you don't want people fiddling with the knobs, you have to remove the knobs.)
– Quuxplusone
Dec 20 '18 at 16:28
Full disclosure:enable_if_t<B, int>=0
is 4 characters shorter thanenable_if_t<B, bool>=true
, andint_if_t<B>=0
would be 4 characters shorter thanbool_if_t<B>=true
. Why did I pickbool
?... I don't know. :P Maybe I should switch toint_if_t
as my primitive.
– Quuxplusone
Dec 20 '18 at 16:30
1
@cukier9a7b5: "I was actually trying to implementsfinae_space_not
andsfinae_space_or
... Unfortunately [I failed] ... I definitely think that, maintainability-wise, it is a must to provide at least syntax fornot
,and
,or
cases." Your thoughts parallel mine! :) I'm about 95% sure that it is impossible to write eithersfinae_not
orsfinae_or
. So, by the laws of syllogism, as you stated: you shouldn't providesfinae_and
, either.
– Quuxplusone
Dec 21 '18 at 22:23
|
show 5 more comments
I just wrote four lines of code...
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
You should have written just two lines of code — either the first two or the last two. Not both. "There's more than one way to [spell] it" is the Perl way, not the C++ way. ;) (At least not intentionally! Not intentionally pre-C++2a! Now we're getting both std::filesystem
and std::fs
, that's a less airtight position.)
As you show, your thing can be used for
template<class T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true>
void foo();
However, this is pretty verbose; surely we'd rather write std::enable_if_t<std::is_integral_v<T> && std::is_arithmetic_v<T>>
than std::enable_if_t<std::is_integral_v<T>>, std::enable_if_t<std::is_arithmetic_v<T>>
.
So personally I have a different alias that accomplishes the same goal: bool_if_t
.
template<bool B>
using bool_if_t = std::enable_if_t<B, bool>;
template<class T,
bool_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>
> = true>
void foo();
This has the advantage that the inner expression is written using normal C++ syntax rules. We don't have to remember some arbitrary rule like "x, y
means both x
and y
must be true"; we just write x && y
. And if we want to enable this function when either x
or y
is true, we don't have to invent a new primitive; we just write bool_if_t<x || y>
. bool_if_t
is much more composable than your sfinae<Ts...>
, because bool_if_t
can exploit the existing expression syntax of C++.
Recommended viewing: "A Soupçon of SFINAE" (Arthur O'Dwyer, CppCon 2017). (Yes, that's me.)
You picked examples that don't really show off the power of sfinae
, by the way.
Consider this example:
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
You take a "value-space" boolean (is_integral_v<T>
), lift it up into "SFINAE-space" with enable_if_t
, and then apply a logical AND operation in SFINAE-space using your sfinae
alias.
template <typename T>
std::enable_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>, T
> const & foo2(T const & val);
Here I take the same "value-space" boolean, perform the logical AND in value space where we have a dedicated operator &&
for maximum expressiveness; and I lift it up into SFINAE-space after doing the AND. This is clearer and also probably more efficient in terms of compile time.
Where your version helps is when your values start in SFINAE-space!
template<class T>
sfinae<T,
decltype(std::declval<T>() + std::declval<T>()),
T*,
T&
> const & foo3(T const & val);
If we were trying to replicate this code's behavior with the standard tools, we'd say something like
template<class, class=void> struct has_plus : std::false_type ;
template<class T> struct has_plus<T, decltype(void(std::declval<T>() + std::declval<T>()))> : std::true_type ;
template<class T> inline constexpr bool has_plus_v = has_plus<T>::value;
template<class T>
std::enable_if_t<
has_plus_v<T> &&
not std::is_reference_v<T> &&
not std::is_void_v<T>, T
> const & foo4(T const & val);
That is, we start with a value in SFINAE-space (decltype(std::declval<T>() + std::declval<T>())
), lift it into value-space (has_plus_v<T>
), do the logical AND in value-space, and lift the result back into SFINAE-space.
Whereas with your foo3
, you start in SFINAE-space, do the logical AND via sfinae<...>
without leaving SFINAE-space, and then you're done. Much simpler! But harder to read, I think.
A simple way to improve readability is to pick a good name. sfinae_space_and<Ts...>
might be clearer. Can you think of a way to write sfinae_space_or<Ts...>
? How about sfinae_space_not<T>
? Does it make sense, maintainability-wise, to provide one without the others?
I just wrote four lines of code...
template<typename T, typename ...>
using sfinae = T;
template<typename T, typename ...>
using SFINAE = T;
You should have written just two lines of code — either the first two or the last two. Not both. "There's more than one way to [spell] it" is the Perl way, not the C++ way. ;) (At least not intentionally! Not intentionally pre-C++2a! Now we're getting both std::filesystem
and std::fs
, that's a less airtight position.)
As you show, your thing can be used for
template<class T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true>
void foo();
However, this is pretty verbose; surely we'd rather write std::enable_if_t<std::is_integral_v<T> && std::is_arithmetic_v<T>>
than std::enable_if_t<std::is_integral_v<T>>, std::enable_if_t<std::is_arithmetic_v<T>>
.
So personally I have a different alias that accomplishes the same goal: bool_if_t
.
template<bool B>
using bool_if_t = std::enable_if_t<B, bool>;
template<class T,
bool_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>
> = true>
void foo();
This has the advantage that the inner expression is written using normal C++ syntax rules. We don't have to remember some arbitrary rule like "x, y
means both x
and y
must be true"; we just write x && y
. And if we want to enable this function when either x
or y
is true, we don't have to invent a new primitive; we just write bool_if_t<x || y>
. bool_if_t
is much more composable than your sfinae<Ts...>
, because bool_if_t
can exploit the existing expression syntax of C++.
Recommended viewing: "A Soupçon of SFINAE" (Arthur O'Dwyer, CppCon 2017). (Yes, that's me.)
You picked examples that don't really show off the power of sfinae
, by the way.
Consider this example:
// 3) as a function return type:
template <typename T>
sfinae<T,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> const & foo(T const & val);
You take a "value-space" boolean (is_integral_v<T>
), lift it up into "SFINAE-space" with enable_if_t
, and then apply a logical AND operation in SFINAE-space using your sfinae
alias.
template <typename T>
std::enable_if_t<
std::is_integral_v<T> &&
std::is_arithmetic_v<T>, T
> const & foo2(T const & val);
Here I take the same "value-space" boolean, perform the logical AND in value space where we have a dedicated operator &&
for maximum expressiveness; and I lift it up into SFINAE-space after doing the AND. This is clearer and also probably more efficient in terms of compile time.
Where your version helps is when your values start in SFINAE-space!
template<class T>
sfinae<T,
decltype(std::declval<T>() + std::declval<T>()),
T*,
T&
> const & foo3(T const & val);
If we were trying to replicate this code's behavior with the standard tools, we'd say something like
template<class, class=void> struct has_plus : std::false_type ;
template<class T> struct has_plus<T, decltype(void(std::declval<T>() + std::declval<T>()))> : std::true_type ;
template<class T> inline constexpr bool has_plus_v = has_plus<T>::value;
template<class T>
std::enable_if_t<
has_plus_v<T> &&
not std::is_reference_v<T> &&
not std::is_void_v<T>, T
> const & foo4(T const & val);
That is, we start with a value in SFINAE-space (decltype(std::declval<T>() + std::declval<T>())
), lift it into value-space (has_plus_v<T>
), do the logical AND in value-space, and lift the result back into SFINAE-space.
Whereas with your foo3
, you start in SFINAE-space, do the logical AND via sfinae<...>
without leaving SFINAE-space, and then you're done. Much simpler! But harder to read, I think.
A simple way to improve readability is to pick a good name. sfinae_space_and<Ts...>
might be clearer. Can you think of a way to write sfinae_space_or<Ts...>
? How about sfinae_space_not<T>
? Does it make sense, maintainability-wise, to provide one without the others?
answered Dec 19 '18 at 18:38
Quuxplusone
11.3k11959
11.3k11959
5
Incredible how much one can write about four (two) lines of code. Very informative ;-)
– t3chb0t
Dec 19 '18 at 18:42
What's the use ofbool_if_t = std::enable_if_t<B, bool>;
? IMO it hides the intent behind it by removing the informative "enable_if" part.
– Flamefire
Dec 20 '18 at 11:56
@Flamefire: I see your point, but OTOH, the "intent" is usually obvious from context. Significantly,bool_if_t<B>=true
is 8 characters shorter thanenable_if_t<B, bool>=true
. Perhaps most significantly, it eliminates a bikeshed point: if all you have is the low-levelenable_if_t
, then you'll see people writingenable_if_t<B, XXX>=
for many different values ofXXX
(bool, int, etc). The slightly-higher-level abstraction eliminates that needless variation. (Murphy's Law: if you don't want people fiddling with the knobs, you have to remove the knobs.)
– Quuxplusone
Dec 20 '18 at 16:28
Full disclosure:enable_if_t<B, int>=0
is 4 characters shorter thanenable_if_t<B, bool>=true
, andint_if_t<B>=0
would be 4 characters shorter thanbool_if_t<B>=true
. Why did I pickbool
?... I don't know. :P Maybe I should switch toint_if_t
as my primitive.
– Quuxplusone
Dec 20 '18 at 16:30
1
@cukier9a7b5: "I was actually trying to implementsfinae_space_not
andsfinae_space_or
... Unfortunately [I failed] ... I definitely think that, maintainability-wise, it is a must to provide at least syntax fornot
,and
,or
cases." Your thoughts parallel mine! :) I'm about 95% sure that it is impossible to write eithersfinae_not
orsfinae_or
. So, by the laws of syllogism, as you stated: you shouldn't providesfinae_and
, either.
– Quuxplusone
Dec 21 '18 at 22:23
|
show 5 more comments
5
Incredible how much one can write about four (two) lines of code. Very informative ;-)
– t3chb0t
Dec 19 '18 at 18:42
What's the use ofbool_if_t = std::enable_if_t<B, bool>;
? IMO it hides the intent behind it by removing the informative "enable_if" part.
– Flamefire
Dec 20 '18 at 11:56
@Flamefire: I see your point, but OTOH, the "intent" is usually obvious from context. Significantly,bool_if_t<B>=true
is 8 characters shorter thanenable_if_t<B, bool>=true
. Perhaps most significantly, it eliminates a bikeshed point: if all you have is the low-levelenable_if_t
, then you'll see people writingenable_if_t<B, XXX>=
for many different values ofXXX
(bool, int, etc). The slightly-higher-level abstraction eliminates that needless variation. (Murphy's Law: if you don't want people fiddling with the knobs, you have to remove the knobs.)
– Quuxplusone
Dec 20 '18 at 16:28
Full disclosure:enable_if_t<B, int>=0
is 4 characters shorter thanenable_if_t<B, bool>=true
, andint_if_t<B>=0
would be 4 characters shorter thanbool_if_t<B>=true
. Why did I pickbool
?... I don't know. :P Maybe I should switch toint_if_t
as my primitive.
– Quuxplusone
Dec 20 '18 at 16:30
1
@cukier9a7b5: "I was actually trying to implementsfinae_space_not
andsfinae_space_or
... Unfortunately [I failed] ... I definitely think that, maintainability-wise, it is a must to provide at least syntax fornot
,and
,or
cases." Your thoughts parallel mine! :) I'm about 95% sure that it is impossible to write eithersfinae_not
orsfinae_or
. So, by the laws of syllogism, as you stated: you shouldn't providesfinae_and
, either.
– Quuxplusone
Dec 21 '18 at 22:23
5
5
Incredible how much one can write about four (two) lines of code. Very informative ;-)
– t3chb0t
Dec 19 '18 at 18:42
Incredible how much one can write about four (two) lines of code. Very informative ;-)
– t3chb0t
Dec 19 '18 at 18:42
What's the use of
bool_if_t = std::enable_if_t<B, bool>;
? IMO it hides the intent behind it by removing the informative "enable_if" part.– Flamefire
Dec 20 '18 at 11:56
What's the use of
bool_if_t = std::enable_if_t<B, bool>;
? IMO it hides the intent behind it by removing the informative "enable_if" part.– Flamefire
Dec 20 '18 at 11:56
@Flamefire: I see your point, but OTOH, the "intent" is usually obvious from context. Significantly,
bool_if_t<B>=true
is 8 characters shorter than enable_if_t<B, bool>=true
. Perhaps most significantly, it eliminates a bikeshed point: if all you have is the low-level enable_if_t
, then you'll see people writing enable_if_t<B, XXX>=
for many different values of XXX
(bool, int, etc). The slightly-higher-level abstraction eliminates that needless variation. (Murphy's Law: if you don't want people fiddling with the knobs, you have to remove the knobs.)– Quuxplusone
Dec 20 '18 at 16:28
@Flamefire: I see your point, but OTOH, the "intent" is usually obvious from context. Significantly,
bool_if_t<B>=true
is 8 characters shorter than enable_if_t<B, bool>=true
. Perhaps most significantly, it eliminates a bikeshed point: if all you have is the low-level enable_if_t
, then you'll see people writing enable_if_t<B, XXX>=
for many different values of XXX
(bool, int, etc). The slightly-higher-level abstraction eliminates that needless variation. (Murphy's Law: if you don't want people fiddling with the knobs, you have to remove the knobs.)– Quuxplusone
Dec 20 '18 at 16:28
Full disclosure:
enable_if_t<B, int>=0
is 4 characters shorter than enable_if_t<B, bool>=true
, and int_if_t<B>=0
would be 4 characters shorter than bool_if_t<B>=true
. Why did I pick bool
?... I don't know. :P Maybe I should switch to int_if_t
as my primitive.– Quuxplusone
Dec 20 '18 at 16:30
Full disclosure:
enable_if_t<B, int>=0
is 4 characters shorter than enable_if_t<B, bool>=true
, and int_if_t<B>=0
would be 4 characters shorter than bool_if_t<B>=true
. Why did I pick bool
?... I don't know. :P Maybe I should switch to int_if_t
as my primitive.– Quuxplusone
Dec 20 '18 at 16:30
1
1
@cukier9a7b5: "I was actually trying to implement
sfinae_space_not
and sfinae_space_or
... Unfortunately [I failed] ... I definitely think that, maintainability-wise, it is a must to provide at least syntax for not
, and
, or
cases." Your thoughts parallel mine! :) I'm about 95% sure that it is impossible to write either sfinae_not
or sfinae_or
. So, by the laws of syllogism, as you stated: you shouldn't provide sfinae_and
, either.– Quuxplusone
Dec 21 '18 at 22:23
@cukier9a7b5: "I was actually trying to implement
sfinae_space_not
and sfinae_space_or
... Unfortunately [I failed] ... I definitely think that, maintainability-wise, it is a must to provide at least syntax for not
, and
, or
cases." Your thoughts parallel mine! :) I'm about 95% sure that it is impossible to write either sfinae_not
or sfinae_or
. So, by the laws of syllogism, as you stated: you shouldn't provide sfinae_and
, either.– Quuxplusone
Dec 21 '18 at 22:23
|
show 5 more comments
A bit dated...
First of all, let me say that since C++20 much of SFINAE will be unnecessary. Concepts have SFINAE enabled by default, have much nicer syntax, and have greater reuse capabilities.
Code Review
Personally I don't see much improvement over original one. What I would prefer instead of
template <typename T, typename ... >
...
is
template <typename T, bool ... Conditionals>
... //perform folding
Even then, people sometimes have non-trivial logic like AND-ing and OR-ing the results of conditionals.
Much of the complexity lies in that conditional statements, which could be moved away by using something like template variable:
template <typename T>
constexpr inline bool supports_xxx = /*is_same, trivial, etc*/;
and then people could just use
template <typename T, typename = std::enable_if_t<supports_xxx<T>>>
...
Example
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
is better written as
template <typename T>
constexpr inline bool is_int_arithmetic_v = std::is_integral_v<T>
&& std::is_arithmetic_v<T>;
template <typename T,
typename = std::enable_if_t<is_int_arithmetic_v<T>>>
class foo;
Do note the inline
, it will help users of the code to deal with ODR issues.
typename = void_t<things...>
doesn't work if you want multiple mutually exclusive templates.bool_if_t<things...> = true
works in that case. See youtube.com/watch?v=ybaE9qlhHvw&t=37m00s .
– Quuxplusone
Dec 19 '18 at 18:41
@Quuxplusone, shouldn't specialization paired with some sort of tag dispatch be used in that case? I believe it also scales better in cases where more than 2 mutually exclusive cases are present. But yeah, my advice is not a good one to make.
– Incomputable
Dec 19 '18 at 18:43
"Shouldn't" is a strong word. ;) I agree that tag dispatch is usually more readable and should be preferred (orif constexpr
in C++17). But sometimes tag dispatch can't be used; see the video's example of a conditionallyexplicit
constructor.
– Quuxplusone
Dec 19 '18 at 18:54
2
Review review: It is "Dated" "since C++20" because it "will be unecessary". Sounds like a plot from Back to the Future IV ;) Anyways, learning SFINAE may still be worthwhile while writing library code that should be backwards compatible and/or useful within older codebases. And compilers still not to catch up a lot 'til they support fully C++20.
– phresnel
Dec 20 '18 at 9:14
1
since C++20
Still a year off! SinceConcepts
were initially proposed for C++11 C++14 and C++17 but dropped before the standard was published I am not going to hold my breath until I see them in the ratified standard.
– Martin York
Dec 20 '18 at 11:36
|
show 1 more comment
A bit dated...
First of all, let me say that since C++20 much of SFINAE will be unnecessary. Concepts have SFINAE enabled by default, have much nicer syntax, and have greater reuse capabilities.
Code Review
Personally I don't see much improvement over original one. What I would prefer instead of
template <typename T, typename ... >
...
is
template <typename T, bool ... Conditionals>
... //perform folding
Even then, people sometimes have non-trivial logic like AND-ing and OR-ing the results of conditionals.
Much of the complexity lies in that conditional statements, which could be moved away by using something like template variable:
template <typename T>
constexpr inline bool supports_xxx = /*is_same, trivial, etc*/;
and then people could just use
template <typename T, typename = std::enable_if_t<supports_xxx<T>>>
...
Example
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
is better written as
template <typename T>
constexpr inline bool is_int_arithmetic_v = std::is_integral_v<T>
&& std::is_arithmetic_v<T>;
template <typename T,
typename = std::enable_if_t<is_int_arithmetic_v<T>>>
class foo;
Do note the inline
, it will help users of the code to deal with ODR issues.
typename = void_t<things...>
doesn't work if you want multiple mutually exclusive templates.bool_if_t<things...> = true
works in that case. See youtube.com/watch?v=ybaE9qlhHvw&t=37m00s .
– Quuxplusone
Dec 19 '18 at 18:41
@Quuxplusone, shouldn't specialization paired with some sort of tag dispatch be used in that case? I believe it also scales better in cases where more than 2 mutually exclusive cases are present. But yeah, my advice is not a good one to make.
– Incomputable
Dec 19 '18 at 18:43
"Shouldn't" is a strong word. ;) I agree that tag dispatch is usually more readable and should be preferred (orif constexpr
in C++17). But sometimes tag dispatch can't be used; see the video's example of a conditionallyexplicit
constructor.
– Quuxplusone
Dec 19 '18 at 18:54
2
Review review: It is "Dated" "since C++20" because it "will be unecessary". Sounds like a plot from Back to the Future IV ;) Anyways, learning SFINAE may still be worthwhile while writing library code that should be backwards compatible and/or useful within older codebases. And compilers still not to catch up a lot 'til they support fully C++20.
– phresnel
Dec 20 '18 at 9:14
1
since C++20
Still a year off! SinceConcepts
were initially proposed for C++11 C++14 and C++17 but dropped before the standard was published I am not going to hold my breath until I see them in the ratified standard.
– Martin York
Dec 20 '18 at 11:36
|
show 1 more comment
A bit dated...
First of all, let me say that since C++20 much of SFINAE will be unnecessary. Concepts have SFINAE enabled by default, have much nicer syntax, and have greater reuse capabilities.
Code Review
Personally I don't see much improvement over original one. What I would prefer instead of
template <typename T, typename ... >
...
is
template <typename T, bool ... Conditionals>
... //perform folding
Even then, people sometimes have non-trivial logic like AND-ing and OR-ing the results of conditionals.
Much of the complexity lies in that conditional statements, which could be moved away by using something like template variable:
template <typename T>
constexpr inline bool supports_xxx = /*is_same, trivial, etc*/;
and then people could just use
template <typename T, typename = std::enable_if_t<supports_xxx<T>>>
...
Example
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
is better written as
template <typename T>
constexpr inline bool is_int_arithmetic_v = std::is_integral_v<T>
&& std::is_arithmetic_v<T>;
template <typename T,
typename = std::enable_if_t<is_int_arithmetic_v<T>>>
class foo;
Do note the inline
, it will help users of the code to deal with ODR issues.
A bit dated...
First of all, let me say that since C++20 much of SFINAE will be unnecessary. Concepts have SFINAE enabled by default, have much nicer syntax, and have greater reuse capabilities.
Code Review
Personally I don't see much improvement over original one. What I would prefer instead of
template <typename T, typename ... >
...
is
template <typename T, bool ... Conditionals>
... //perform folding
Even then, people sometimes have non-trivial logic like AND-ing and OR-ing the results of conditionals.
Much of the complexity lies in that conditional statements, which could be moved away by using something like template variable:
template <typename T>
constexpr inline bool supports_xxx = /*is_same, trivial, etc*/;
and then people could just use
template <typename T, typename = std::enable_if_t<supports_xxx<T>>>
...
Example
// 1) within class declaration:
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
is better written as
template <typename T>
constexpr inline bool is_int_arithmetic_v = std::is_integral_v<T>
&& std::is_arithmetic_v<T>;
template <typename T,
typename = std::enable_if_t<is_int_arithmetic_v<T>>>
class foo;
Do note the inline
, it will help users of the code to deal with ODR issues.
edited Dec 19 '18 at 18:49
answered Dec 19 '18 at 18:20
Incomputable
6,59021653
6,59021653
typename = void_t<things...>
doesn't work if you want multiple mutually exclusive templates.bool_if_t<things...> = true
works in that case. See youtube.com/watch?v=ybaE9qlhHvw&t=37m00s .
– Quuxplusone
Dec 19 '18 at 18:41
@Quuxplusone, shouldn't specialization paired with some sort of tag dispatch be used in that case? I believe it also scales better in cases where more than 2 mutually exclusive cases are present. But yeah, my advice is not a good one to make.
– Incomputable
Dec 19 '18 at 18:43
"Shouldn't" is a strong word. ;) I agree that tag dispatch is usually more readable and should be preferred (orif constexpr
in C++17). But sometimes tag dispatch can't be used; see the video's example of a conditionallyexplicit
constructor.
– Quuxplusone
Dec 19 '18 at 18:54
2
Review review: It is "Dated" "since C++20" because it "will be unecessary". Sounds like a plot from Back to the Future IV ;) Anyways, learning SFINAE may still be worthwhile while writing library code that should be backwards compatible and/or useful within older codebases. And compilers still not to catch up a lot 'til they support fully C++20.
– phresnel
Dec 20 '18 at 9:14
1
since C++20
Still a year off! SinceConcepts
were initially proposed for C++11 C++14 and C++17 but dropped before the standard was published I am not going to hold my breath until I see them in the ratified standard.
– Martin York
Dec 20 '18 at 11:36
|
show 1 more comment
typename = void_t<things...>
doesn't work if you want multiple mutually exclusive templates.bool_if_t<things...> = true
works in that case. See youtube.com/watch?v=ybaE9qlhHvw&t=37m00s .
– Quuxplusone
Dec 19 '18 at 18:41
@Quuxplusone, shouldn't specialization paired with some sort of tag dispatch be used in that case? I believe it also scales better in cases where more than 2 mutually exclusive cases are present. But yeah, my advice is not a good one to make.
– Incomputable
Dec 19 '18 at 18:43
"Shouldn't" is a strong word. ;) I agree that tag dispatch is usually more readable and should be preferred (orif constexpr
in C++17). But sometimes tag dispatch can't be used; see the video's example of a conditionallyexplicit
constructor.
– Quuxplusone
Dec 19 '18 at 18:54
2
Review review: It is "Dated" "since C++20" because it "will be unecessary". Sounds like a plot from Back to the Future IV ;) Anyways, learning SFINAE may still be worthwhile while writing library code that should be backwards compatible and/or useful within older codebases. And compilers still not to catch up a lot 'til they support fully C++20.
– phresnel
Dec 20 '18 at 9:14
1
since C++20
Still a year off! SinceConcepts
were initially proposed for C++11 C++14 and C++17 but dropped before the standard was published I am not going to hold my breath until I see them in the ratified standard.
– Martin York
Dec 20 '18 at 11:36
typename = void_t<things...>
doesn't work if you want multiple mutually exclusive templates. bool_if_t<things...> = true
works in that case. See youtube.com/watch?v=ybaE9qlhHvw&t=37m00s .– Quuxplusone
Dec 19 '18 at 18:41
typename = void_t<things...>
doesn't work if you want multiple mutually exclusive templates. bool_if_t<things...> = true
works in that case. See youtube.com/watch?v=ybaE9qlhHvw&t=37m00s .– Quuxplusone
Dec 19 '18 at 18:41
@Quuxplusone, shouldn't specialization paired with some sort of tag dispatch be used in that case? I believe it also scales better in cases where more than 2 mutually exclusive cases are present. But yeah, my advice is not a good one to make.
– Incomputable
Dec 19 '18 at 18:43
@Quuxplusone, shouldn't specialization paired with some sort of tag dispatch be used in that case? I believe it also scales better in cases where more than 2 mutually exclusive cases are present. But yeah, my advice is not a good one to make.
– Incomputable
Dec 19 '18 at 18:43
"Shouldn't" is a strong word. ;) I agree that tag dispatch is usually more readable and should be preferred (or
if constexpr
in C++17). But sometimes tag dispatch can't be used; see the video's example of a conditionally explicit
constructor.– Quuxplusone
Dec 19 '18 at 18:54
"Shouldn't" is a strong word. ;) I agree that tag dispatch is usually more readable and should be preferred (or
if constexpr
in C++17). But sometimes tag dispatch can't be used; see the video's example of a conditionally explicit
constructor.– Quuxplusone
Dec 19 '18 at 18:54
2
2
Review review: It is "Dated" "since C++20" because it "will be unecessary". Sounds like a plot from Back to the Future IV ;) Anyways, learning SFINAE may still be worthwhile while writing library code that should be backwards compatible and/or useful within older codebases. And compilers still not to catch up a lot 'til they support fully C++20.
– phresnel
Dec 20 '18 at 9:14
Review review: It is "Dated" "since C++20" because it "will be unecessary". Sounds like a plot from Back to the Future IV ;) Anyways, learning SFINAE may still be worthwhile while writing library code that should be backwards compatible and/or useful within older codebases. And compilers still not to catch up a lot 'til they support fully C++20.
– phresnel
Dec 20 '18 at 9:14
1
1
since C++20
Still a year off! Since Concepts
were initially proposed for C++11 C++14 and C++17 but dropped before the standard was published I am not going to hold my breath until I see them in the ratified standard.– Martin York
Dec 20 '18 at 11:36
since C++20
Still a year off! Since Concepts
were initially proposed for C++11 C++14 and C++17 but dropped before the standard was published I am not going to hold my breath until I see them in the ratified standard.– Martin York
Dec 20 '18 at 11:36
|
show 1 more comment
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
vs
template <typename T,
std::enable_if_t<
std::is_integral_v<T>
&& std::is_arithmetic_v<T>,
bool
> = true
class foo;
This version doesn't use your new sfinae
template, and uses fewer characters, and seems no less functional.
What more in C++20 we'll have named concepts (and failing that) requires clauses. That will make much SFINAE obsolete.
What more, there are fancier ways to do SFINAE, especially in c++20
There is this:
template<template<class...>class, class...>
struct can_apply;
which tests if a template can be applied to a list of types.
With constexpr
lambdas that can appear in template non type parameter argument calculations, we can do:
template<class F>
constexpr auto apply_test(F &&);
which returns an object that, when evaluated on some arguments, returns true_type
if you can invoke F
on it, and false_type
otherwise.
template<class T,
std::enable_if_t<
apply_test((auto a, auto b)RETURNS(a+b))( std::declval<T>(), std::declval<T>() ),
bool
> = true
>
struct foo;
here we test if a type T
can be added to itself. (I also use the somewhat ubiquitous RETURNS
macro)
Or, more cleanly:
template<class T>
auto foo( T const& lhs, T const& rhs )
requires test_apply(std::plus<T>)(lhs, rhs)
assuming sufficiently SFINAE friendly std::plus<T>
.
add a comment |
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
vs
template <typename T,
std::enable_if_t<
std::is_integral_v<T>
&& std::is_arithmetic_v<T>,
bool
> = true
class foo;
This version doesn't use your new sfinae
template, and uses fewer characters, and seems no less functional.
What more in C++20 we'll have named concepts (and failing that) requires clauses. That will make much SFINAE obsolete.
What more, there are fancier ways to do SFINAE, especially in c++20
There is this:
template<template<class...>class, class...>
struct can_apply;
which tests if a template can be applied to a list of types.
With constexpr
lambdas that can appear in template non type parameter argument calculations, we can do:
template<class F>
constexpr auto apply_test(F &&);
which returns an object that, when evaluated on some arguments, returns true_type
if you can invoke F
on it, and false_type
otherwise.
template<class T,
std::enable_if_t<
apply_test((auto a, auto b)RETURNS(a+b))( std::declval<T>(), std::declval<T>() ),
bool
> = true
>
struct foo;
here we test if a type T
can be added to itself. (I also use the somewhat ubiquitous RETURNS
macro)
Or, more cleanly:
template<class T>
auto foo( T const& lhs, T const& rhs )
requires test_apply(std::plus<T>)(lhs, rhs)
assuming sufficiently SFINAE friendly std::plus<T>
.
add a comment |
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
vs
template <typename T,
std::enable_if_t<
std::is_integral_v<T>
&& std::is_arithmetic_v<T>,
bool
> = true
class foo;
This version doesn't use your new sfinae
template, and uses fewer characters, and seems no less functional.
What more in C++20 we'll have named concepts (and failing that) requires clauses. That will make much SFINAE obsolete.
What more, there are fancier ways to do SFINAE, especially in c++20
There is this:
template<template<class...>class, class...>
struct can_apply;
which tests if a template can be applied to a list of types.
With constexpr
lambdas that can appear in template non type parameter argument calculations, we can do:
template<class F>
constexpr auto apply_test(F &&);
which returns an object that, when evaluated on some arguments, returns true_type
if you can invoke F
on it, and false_type
otherwise.
template<class T,
std::enable_if_t<
apply_test((auto a, auto b)RETURNS(a+b))( std::declval<T>(), std::declval<T>() ),
bool
> = true
>
struct foo;
here we test if a type T
can be added to itself. (I also use the somewhat ubiquitous RETURNS
macro)
Or, more cleanly:
template<class T>
auto foo( T const& lhs, T const& rhs )
requires test_apply(std::plus<T>)(lhs, rhs)
assuming sufficiently SFINAE friendly std::plus<T>
.
template <typename T,
sfinae<bool,
std::enable_if_t<std::is_integral_v<T>>,
std::enable_if_t<std::is_arithmetic_v<T>>
> = true /* provide default option, bc reasons */>
class foo;
vs
template <typename T,
std::enable_if_t<
std::is_integral_v<T>
&& std::is_arithmetic_v<T>,
bool
> = true
class foo;
This version doesn't use your new sfinae
template, and uses fewer characters, and seems no less functional.
What more in C++20 we'll have named concepts (and failing that) requires clauses. That will make much SFINAE obsolete.
What more, there are fancier ways to do SFINAE, especially in c++20
There is this:
template<template<class...>class, class...>
struct can_apply;
which tests if a template can be applied to a list of types.
With constexpr
lambdas that can appear in template non type parameter argument calculations, we can do:
template<class F>
constexpr auto apply_test(F &&);
which returns an object that, when evaluated on some arguments, returns true_type
if you can invoke F
on it, and false_type
otherwise.
template<class T,
std::enable_if_t<
apply_test((auto a, auto b)RETURNS(a+b))( std::declval<T>(), std::declval<T>() ),
bool
> = true
>
struct foo;
here we test if a type T
can be added to itself. (I also use the somewhat ubiquitous RETURNS
macro)
Or, more cleanly:
template<class T>
auto foo( T const& lhs, T const& rhs )
requires test_apply(std::plus<T>)(lhs, rhs)
assuming sufficiently SFINAE friendly std::plus<T>
.
answered Dec 19 '18 at 19:47
Yakk
53339
53339
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209992%2fsfinae-proposal-useful-or-unnecessary%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Please turn all
...
into actual code. We review only complete snippets.– t3chb0t
Dec 19 '18 at 17:43
7
@t3chb0t, but the actual code is
template<typename T, typename ...> using sfinae = T;
andtemplate<typename T, typename ...> using SFINAE = T;
everything else is just a usage examples... Also because of the keywords "immediate context" (which can be found in cpp draw, thesfinae
section. Usage of this aliases is limited declarations only. However... I could delete definitions (it would by erase all...
by an extension).– cukier9a7b5
Dec 19 '18 at 17:47
Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site. Please take a look at the help center.
– Mast
Dec 19 '18 at 17:48
I know, inside templates yes, but there are many
...
elsewhere and I now admit that I didn't see the// usage examples:
comment. I'd be better if it was a heading so that it's better visible... let me edit this...– t3chb0t
Dec 19 '18 at 17:50
1
I think your quesiton is fine after all. Not much code but the rest is just examples that weren't easy to notice. I'll retract my close-vote now... @Mast?
– t3chb0t
Dec 19 '18 at 17:52