www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - function template specialization question D vs. C++

reply kdevel <kdevel vogtner.de> writes:
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
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
parent reply kdevel <kdevel vogtner.de> writes:
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
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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:
 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?
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 Davis
Jan 13 2018
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
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
next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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:
 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
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 Davis
Jan 13 2018
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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:
 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
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.
It is a neat trick though. - Jonathan M Davis
Jan 13 2018
prev sibling parent kdevel <kdevel vogtner.de> writes:
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:
 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
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()
Jan 14 2018
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
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