digitalmars.D.learn - function template specialization question D vs. C++
- kdevel (79/79) Jan 13 2018 fusp.d
- Nicholas Wilson (9/45) Jan 13 2018 The `:` is not a type equality check. It is more like a "converts
- kdevel (3/6) Jan 13 2018 Thanks. That works but looks a bit ugly. Am I right that I have
- Jonathan M Davis (69/76) Jan 13 2018 If you're using template constraints rather than template specialization...
- Adam D. Ruppe (4/7) Jan 13 2018 Not true: see the tip of the week here
- Jonathan M Davis (10/17) Jan 13 2018 Well, that's a neat trick to get around the restriction that I stated, b...
- Jonathan M Davis (4/23) Jan 13 2018 It is a neat trick though.
- kdevel (32/39) Jan 14 2018 Thanks for that hint (self specialization).
- Adam D. Ruppe (22/24) Jan 13 2018 Yes, it does., and it works for float and double. Just `real`
fusp.d ``` import std.stdio; import std.typecons; void foo (T) () { writeln ("(1) foo T = ", T.stringof); } void foo (T : float) () { writeln ("(2) foo T = ", T.stringof); } // void foo (T : double) () // { // writeln ("(2) foo T = ", T.stringof); // } void main () { foo!float; foo!double; foo!real; foo!string; } ``` prints (2) foo T = float (2) foo T = double (2) foo T = real (1) foo T = string I would have expected (2) foo T = float (1) foo T = double (1) foo T = real (1) foo T = string The compiler does not allow me to specialize the primary function template for double: $ dmd fusp.d fusp.d(23): Error: fusp.foo called with argument types () matches both: fusp.d(9): fusp.foo!real.foo() and: fusp.d(14): fusp.foo!real.foo() fusp.d(23): Error: foo!real has no effect Is this a compiler error? C++ function template specialization works as expected: fusp.cc ``` #include <iostream> template<typename T> void foo () { std::cout << "(1) " << __PRETTY_FUNCTION__ << "\n"; } template<> void foo<float> () { std::cout << "(2) " << __PRETTY_FUNCTION__ << "\n"; } template<> void foo<double> () { std::cout << "(3) " << __PRETTY_FUNCTION__ << "\n"; } int main () { foo<int> (); foo<float> (); foo<double> (); foo<long double> (); foo<std::string> (); } ``` $ g++ fusc.cc $ ./a.out (1) void foo() [with T = int] (2) void foo() [with T = float] (3) void foo() [with T = double] (1) void foo() [with T = long double] (1) void foo() [with T = std::__cxx11::basic_string<char>]
Jan 13 2018
On Sunday, 14 January 2018 at 00:09:42 UTC, kdevel wrote:fusp.d ``` import std.stdio; import std.typecons; void foo (T) () { writeln ("(1) foo T = ", T.stringof); } void foo (T : float) () { writeln ("(2) foo T = ", T.stringof); } // void foo (T : double) () // { // writeln ("(2) foo T = ", T.stringof); // } void main () { foo!float; foo!double; foo!real; foo!string; } ``` prints (2) foo T = float (2) foo T = double (2) foo T = real (1) foo T = string I would have expected (2) foo T = float (1) foo T = double (1) foo T = real (1) foo T = string The compiler does not allow me to specialize the primary function template for double:The `:` is not a type equality check. It is more like a "converts to" or "is of the form of". i.e. `void foo (T : float) ()` reads as "foo is a template function returning void, taking one template type T that converts to float, and no runtime args." The usual way to do what you are trying to do is with template constraints. void foo(T)() if (is(T== float)) { ...}
Jan 13 2018
On Sunday, 14 January 2018 at 00:30:37 UTC, Nicholas Wilson wrote:The usual way to do what you are trying to do is with template constraints. void foo(T)() if (is(T== float)) { ...}Thanks. That works but looks a bit ugly. Am I right that I have to leave out the primary (unconstrained) template?
Jan 13 2018
On Sunday, January 14, 2018 01:02:46 kdevel via Digitalmars-d-learn wrote:On Sunday, 14 January 2018 at 00:30:37 UTC, Nicholas Wilson wrote:If you're using template constraints rather than template specializations, then you can't have any unconstrained templates. A given argument will have to compile with exactly one overload of the template - or none, if you don't want it to compile with any of them. However, you can also specialize internally via static if, which may or may not be better, depending on what you're trying to do. e.g. void foo(T)(T arg) { static if(is(T == int)) { // do something } else static if(is(T == float)) { // do something else } else { // do something with the rest... } } To do the same thing with overloaded templates, you'd need to do void foo(T)(T arg) if(is(T == int)) { // do something } void foo(T)(T arg) if(is(T == float)) { // do something else } void foo(T)(T arg) if(!is(T == int) && !is(T == float)) { // do something with the rest... } In general, at this point, it's considered best practice to try and make the top-level constraints as general as possible and specialize internally rather than overloading at the top-level (unless the overloads really are disjoint - e.g. they take a different number of arguments). Doing all of the specializations at the top-level has made the documentation for some of the functions in Phobos harder to read through and made error messages worse, since you have to dig through a bunch of different template constraints, whereas by making the top-level constraints more general and trying to push the specializations inside with static ifs, things become more manageable. Also, if you want to T to be implicitly convertibly to the given type, then use : instead of ==, and if you don't care about the type modifiers such as const, then use std.traits.Unqual. e.g. void foo(T)(T arg) if(is(T == float)) { } will not compile if the argument is a const float, whereas void foo(T)(T arg) if(is(Unqual!T == float)) { } will. And in the case of the static if above, a const float would take the else branch. So, Unqual would probably be merited there as well. For better or worse, D templates tend to end up with a fair bit of metaprogramming used with constraints and static ifs. You're not going to see very many cases of templates without any constraints or specializations (and most folks don't use specializations). You get by far the best control using template constraints, and you can improve error messages by making it so that arguments that wouldn't compile with the template fail the constraint instead of generating an error from inside the template. - Jonathan M DavisThe usual way to do what you are trying to do is with template constraints. void foo(T)() if (is(T== float)) { ...}Thanks. That works but looks a bit ugly. Am I right that I have to leave out the primary (unconstrained) template?
Jan 13 2018
On Sunday, 14 January 2018 at 02:14:50 UTC, Jonathan M Davis wrote:If you're using template constraints rather than template specializations, then you can't have any unconstrained templates.Not true: see the tip of the week here http://arsdnet.net/this-week-in-d/2016-sep-04.html
Jan 13 2018
On Sunday, January 14, 2018 02:24:52 Adam D. Ruppe via Digitalmars-d-learn wrote:On Sunday, 14 January 2018 at 02:14:50 UTC, Jonathan M Davis wrote:Well, that's a neat trick to get around the restriction that I stated, but it doesn't really make what I said untrue. Unless you're using template specializations, you can't overload an unconstrained template. That tip just shows a way to combine template specializations and template constraints so that you get the same effect as if you were allowed to overload unconstrained templates with constrained templates without using template specializations. - Jonathan M DavisIf you're using template constraints rather than template specializations, then you can't have any unconstrained templates.Not true: see the tip of the week here http://arsdnet.net/this-week-in-d/2016-sep-04.html
Jan 13 2018
On Saturday, January 13, 2018 19:32:06 Jonathan M Davis via Digitalmars-d- learn wrote:On Sunday, January 14, 2018 02:24:52 Adam D. Ruppe via Digitalmars-d-learn wrote:It is a neat trick though. - Jonathan M DavisOn Sunday, 14 January 2018 at 02:14:50 UTC, Jonathan M Davis wrote:Well, that's a neat trick to get around the restriction that I stated, but it doesn't really make what I said untrue. Unless you're using template specializations, you can't overload an unconstrained template. That tip just shows a way to combine template specializations and template constraints so that you get the same effect as if you were allowed to overload unconstrained templates with constrained templates without using template specializations.If you're using template constraints rather than template specializations, then you can't have any unconstrained templates.Not true: see the tip of the week here http://arsdnet.net/this-week-in-d/2016-sep-04.html
Jan 13 2018
On Sunday, 14 January 2018 at 02:24:52 UTC, Adam D. Ruppe wrote:On Sunday, 14 January 2018 at 02:14:50 UTC, Jonathan M Davis wrote:Thanks for that hint (self specialization). cppcompat.d ``` import std.stdio; void foo (T) () { writeln ("(1) ", __PRETTY_FUNCTION__); } void foo (T: T) () if (is (T == float)) { writeln ("(2) ", __PRETTY_FUNCTION__); } void foo (T: T) () if (is (T == double)) { writeln ("(3) ", __PRETTY_FUNCTION__); } void main () { foo!int; foo!float; foo!double; foo!real; foo!string; } ``` $ ./cppcomat (1) void cppcompat.foo!int.foo() (2) void cppcompat.foo!float.foo() (3) void cppcompat.foo!double.foo() (1) void cppcompat.foo!real.foo() (1) void cppcompat.foo!string.foo()If you're using template constraints rather than template specializations, then you can't have any unconstrained templates.Not true: see the tip of the week here http://arsdnet.net/this-week-in-d/2016-sep-04.html
Jan 14 2018
On Sunday, 14 January 2018 at 00:09:42 UTC, kdevel wrote:The compiler does not allow me to specialize the primary function template for double:Yes, it does., and it works for float and double. Just `real` matches both equally, so that's the error for that type. Let me quote the spec: https://dlang.org/spec/template.html#parameters_specialization "The template picked to instantiate is the one that is most specialized that fits the types of the TemplateArgumentList. Determining which is more specialized is done in the same way as the C++ partial ordering rules. If the result is ambiguous, it is an error." So, if there's only T:float, float, double, and real can all be implicitly converted to that, so it is an exact match for float, but a half-match for double and real, which are still better than unspecialized T, so it matches that. When you have T:float and T:double, then double, being an exact match for the second one, triggers that. Similarly, float is still an exact match for the first one, so that's the best. But real is a half-match for both - it implicitly converts, but is not exactly either. So it is ambiguous - the compiler doesn't know which is a best fit. You can add T:real to exactly match that, or use a constraint to filter out real on one or both to remove it from consideration.
Jan 13 2018