Sign in to follow this  

Template Specialization

This topic is 669 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I has a function:

template <class T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
T function(const std::string& requestString) {
  //things happen
}

Clearly it's only supposed to accept numeric types, but now I want to add a specialization so that it can also handle std::string using different internal logic.

 

I haven't messed with specialization before, but what I tried based on my reading was this:

template<> std::string function<std::string>(const std::string& requestString) {
  //tomfoolery
}

The compiler doesn't seem to like that. It says that the template can't match the type. I'm thinking this means that it's upset because std::string isn't arithmetic, as is specified by the original. I figured I should declare an "empty" template (just <class T> and no function body contents) and change the arithmetic one to a specialization, but then (predictably) it complained that <int> was ambiguous between the empty one and the arithmetic one.

 

Anyone know how I can do this?

Share this post


Link to post
Share on other sites
template<typename T>
class acceptsType
{
    template<typename T2, bool isAritmetic = false>
    class helper
    {
    public:

        static const bool value = false;
    };

    template<typename T2>
    class helper<T2, true> // every aritmetic value is accepted
    {
    public:

        static const bool value = true;
    };

    template<>
    class helper<std::string, false> // otherwise, only std::string is accepted
    {
    public:
        static const bool value = true;
    };

public:

    static const bool value = typename helper<T, std::is_arithmetic<T>::value>::value;
};

The idea I had involved generating a helper-template class, that acts as a replacement of "is_aritmetic" when you declare your template function:

template<class T, typename = typename std::enable_if<acceptsType<T>::value, T>::type> // exchange here
T function(const std::string& requestString)
{
    //things happen
}

template<>
std::string function<std::string>(const std::string& requestString) // compiles now
{
    //tomfoolery
}

template<>
int function<int>(const std::string& requestString)
{
}

This compiles for me. I don't know if its the smoothest solution, but at least it gives you explicit control over ie. if you want to add more types without it getting too complicated.

 

EDIT:

 

Also, unless you are stuck on pre-C++11, why even go to the trouble of handling this using std::enable_if? If I get it correctly you want to have an compile error if an user tries to use a type thats not aritmetic(or std::string). Why not use an static_assert instead?

template<class T>
T function(const std::string& requestString)
{
    static_assert(std::is_arithmetic<T>::value, "Function only accepts aritmetic values (int, float, ...). Alternatively, try the std::string overload.");
}

Allows you to have an error message that is more clear, and makes specializing way easier.

Edited by Juliean

Share this post


Link to post
Share on other sites

Also, unless you are stuck on pre-C++11, why even go to the trouble of handling this using std::enable_if? If I get it correctly you want to have an compile error if an user tries to use a type thats not aritmetic(or std::string). Why not use an static_assert instead?


std::enable_if is already C++11. You might be thinking of the old boost::enable_if. That said, I'm not sure about the exact semantics of static_assert but can it really be used to trigger SFINAE, because that's usually the whole point of enable_if.

Khatharr: You might want to take a look at the notes in the cppreference.com documentation. It's not the exact case but might be part of the problem.
I would also try to use enable_if like this

template <class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type function(const std::string& requestString) // ...
instead. The only times I have used it, I used it like that.

Share this post


Link to post
Share on other sites


std::enable_if is already C++11. You might be thinking of the old boost::enable_if. That said, I'm not sure about the exact semantics of static_assert but can it really be used to trigger SFINAE, because that's usually the whole point of enable_if.

 

Ah right, wasn't sure if enable_if is already in C++11 or not.

 

No, static_assert won't trigger SFINAE, but that was kind of my point - as with the code example given, I don't see much points in him using SFINEA. What he shows and describes is a function that should fail to instantiate if a incompatible type is used - which is what static_assert does, but only with a better error output. I posted both my solution and the comment towards static_assert based on whether he really wants to use SFINEA here, or not :)

Share this post


Link to post
Share on other sites
I would use SFINAE like this:
template<typename T> void Func(T x, typename std::enable_if<std::is_arithmetic<T>::value, bool>::type = true) {
	cout << "interger type = " << typeid(T).name() << endl;
	}

void Func(std::string) {
	cout << "string type" << endl;
	}
And beaten to it by BitMaster. You can use enable_if as either a parameter passed (often using a default value that is never used) or as a return value. Whichever syntax you prefer (both are kinda messy IMHO). Edited by Ryan_001

Share this post


Link to post
Share on other sites

No, static_assert won't trigger SFINAE, but that was kind of my point - as with the code example given, I don't see much points in him using SFINEA. What he shows and describes is a function that should fail to instantiate if a incompatible type is used - which is what static_assert does, but only with a better error output. I posted both my solution and the comment towards static_assert based on whether he really wants to use SFINEA here, or not smile.png


Yes, but the point of template specialization is in general that you don't want to just restrict it to specific types, you want radically different behavior for some types which you cannot do unless you provide a completely different implementation for those types.
Edit: Actually I think I see what you want to do now. Specialize for std::string as originally planned but insert the static_assert into the unspecialized template part. Yes, that would work. Although if I had the time I would try to investigate why the current scheme does not work. You don't always have such a workaround and there might be some easy skill points to get.

Ryan_001: that won't work in this scenario since the template parameter is only used for the return value and you have to call it function<myType>(...). If the template parameter was used as a function parameter, it could be done like that. Edited by BitMaster

Share this post


Link to post
Share on other sites


Yes, but the point of template specialization is in general that you don't want to just restrict it to specific types, you want radically different behavior for some types which you cannot do unless you provide a completely different implementation for those types.

 

Sure, I agree, might even been that I misunderstood the original question a bit. I thought he wanted to support only is_aritmetic for the base function, and have only a limitied array of other specialisations (of previously known types) like std::string.

 

In the other case, that he only wants the base function not being callable other than with is_aritmetic compatible types, while still being able to add an arbitrary number of specialisations (possibly by other users), I'd make an even stronger argument for static_assert and against SFINEA.

 

I'm feeling even more confused about the use of std::enable_if now in the first place, so unless I'm right and its misused here, somebody please explain to me what it does in this particaluar code example.

Share this post


Link to post
Share on other sites

In the existing function it prevents compilation if the type is not arithmetic. The function body handles arithmetic types, but I also want to start supporting specific non-arithmetic types using the same signature but different function bodies. (Beginning with std::string, yes.)

 

This is in VS, so the way I have it now does the red underline at the call site when a non-supported type is entered, which is sort of what I'm after.

 

How would you go about using static_assert for this?

Share this post


Link to post
Share on other sites
I don't think one can decide whether it's misused or not without more context. The thing I would really try to fix it is my suggestion from #3. I have never seen any enable_if used as a defaulted template parameter before and that note from cppreference makes me doubly cautious about that...

Share this post


Link to post
Share on other sites

How would you go about using static_assert for this?


template <typename T> T function(const std::string& requestString) 
{
   static_assert(std::is_arithmetic<T>::value, "...");
   // ...
}

template<> std::string function<std::string>(const std::string& requestString) 
{

}
Should work in my opinion but I don't have a relevant compiler close enough to check.

Share this post


Link to post
Share on other sites


In the existing function it prevents compilation if the type is not arithmetic. The function body handles arithmetic types, but I also want to start supporting specific non-arithmetic types using the same signature but different function bodies. (Beginning with std::string, yes.)



This is in VS, so the way I have it now does the red underline at the call site when a non-supported type is entered, which is sort of what I'm after.



How would you go about using static_assert for this?

 

Okay, so at least I was right about the purpose.

 


Should work in my opinion but I don't have a relevant compiler close enough to check.

 

Unforunately what this doesn't do is add the red underline in VS, at least from what I tested with VS2015.

 

So in case you are the only one adding specialisations to this function, you can use my first suggestion, otherwise BitMasters' (if it fixes the problem).

 

Aside from that, you'll have to think about what you prefer: Red underlines wherever someone uses an imcompatible type with this function, probably followed by a cryptic template-error-message (using SFINEA), or no underlining but a meaningful error message that you yourself can provide for the user (using static_assert). Probably depends on your use-case, which of the two is better.

Share this post


Link to post
Share on other sites

The error message for the original was:

1>  main.cpp
1>c:\users\khatharr\documents\visual studio 2015\projects\scratchpad\scratchpad\main.cpp(36): error C2672: 'getUserValue': no matching overloaded function found
1>c:\users\khatharr\documents\visual studio 2015\projects\scratchpad\scratchpad\main.cpp(36): error C2783: 'T getUserValue(const std::string &)': could not deduce template argument for '<unnamed-symbol>'
1>  c:\users\khatharr\documents\visual studio 2015\projects\scratchpad\scratchpad\main.cpp(5): note: see declaration of 'getUserValue'

The static assert version is:

1>  main.cpp
1>c:\users\khatharr\documents\visual studio 2015\projects\scratchpad\scratchpad\main.cpp(7): error C2338: Unsupported type!
1>  c:\users\khatharr\documents\visual studio 2015\projects\scratchpad\scratchpad\main.cpp(38): note: see reference to function template instantiation 'T getUserValue<std::string>(const std::string &)' being compiled
1>          with
1>          [
1>              T=std::string
1>          ]

So it's more or less explicit message versus custom message. I'm going to try a trick here. I'll let you know if it works.

--

No dice. I'll try implementing the helper class.

 

Also...

 

WTF, ISO?

 

--

 

Helper class works correctly, but I'm sitting here staring at it wondering what the hell I'm looking at. Could you explain this to me?

Edited by Khatharr

Share this post


Link to post
Share on other sites


Helper class works correctly, but I'm sitting here staring at it wondering what the hell I'm looking at. Could you explain this to me?

 

Sure do.

 

So first of our outer class should act as a replacement of std::is_aritmetic, so we set it up the interface like this:

template<typename T>
class acceptsType
{
public:
    static const bool value = //???
};

So the value of the compile-time evaluated "value" variable should depend both on the type, as well as the value of is_aritmetic. So we do just that, by defining an inner helper class that takes two template arguments:

template<typename T2, bool isAritmetic = false>
class helper
{
public:

    static const bool value = false;
};

So now we call it by making it take <T, std::is_aritmetic<T>::value>, and assigning its "value" to the outer-class "value":

static const bool value = typename helper<T, std::is_arithmetic<T>::value>::value;

And the base template "helper" class now defines that for any type where is_arithmetic is "false", "value" will also be false. But we want to support some types though, so we add a specialisation for every arbitrary type where is_arithmetic is actually true:

template<typename T2>
class helper<T2, true>
{
public:

    static const bool value = true;
};

This uses partial specialisation, and translates to what I've just said - arbitary T2, and std::is_arthmetic == true will generate this overload.

 

So at this state adding anything except int, float, ... will return "false" and thus fail your SFINEA. But adding additional types is now really easy.

template<>
class helper<std::string, false>
{
public:
    static const bool value = true;
};

This time, we use a full specialistation - we know the value of T2 (std::string), and that it is not arithmetic. This combats the ambiquity that you described in your attempts to fix it - due to the second template parameter, you always get a closes match for the types.

 

 

 

Hope this solves any questions, if you are still confused, I'll try to explain anything that is left over.

Share this post


Link to post
Share on other sites

Okay. Earlier I didn't see that the first helper was something like a prototype that the other two built from. The 'isAritmetic' label confused me too, but now that I'm awake and have read your explanation I can see it. This is a good solution, but I'm kind of depressed that it takes this much to do something that seems like it should be very straightforward. Maybe a future revision of the standard will give us better specialization support.

 

Then again, I'm doing it kind of weird. I figured out that I could have specialized the original version with <std::string, std::string> but then the same double-up would be required at call sites.

Share this post


Link to post
Share on other sites

This topic is 669 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this