2

I'd like to pass a char to my function (fn). I DO NOT want ints and shorts to be typecast into it. So i figure i should have the value be implicitly cast into MyInt (which will only support char) then pass into fn. This should work bc IIRC only one typcast is allowed when passing onto function (so char->MyInt is ok while int->char->MyInt shouldn't).

However it appears both int and char work so i figure another layer of indirection (MyInt2) would fix it. Now they both can not be passed into fn... Is there a way where i can have chars passed in but not int?

#include <cstdio>
struct MyInt2{
    int v;
    MyInt2(char vv){v=vv;}
    MyInt2(){}
};
struct MyInt{
    MyInt2 v;
    MyInt(MyInt2 vv){v=vv;}
};
void fn(MyInt a){
    printf("%d", a.v);
}
int main() {
    fn(1);       //should cause error
    fn((char)2); //should NOT cause error
}

3 Answers3

11

Function templates to the rescue:

template<typename T> void fn(T t);

void fn (char a){
    printf("%c", a);
}

If someone attempts to call fn with anything other than a char argument the function template will be chosen as a match and the linker will complain that it cannot find the appropriate specialization.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • Great answer as well. I cant pick between you two : X –  Jul 11 '12 at 10:24
  • lol +1. That could be his you know. If lets say I have 2 functions or 3 functions that require this ;) –  Jul 11 '12 at 10:26
  • Gah! [Function templates specialisations are evil](http://www.gotw.ca/publications/mill17.htm)! – gwiazdorrr Jul 11 '12 at 11:18
  • @gwiazdorrr: No they are not (how on earth do you reach this conclusion from Sutter's writeup?!?). They can be misused, which is the point Sutter wants to make, but in this case a template is a perfectly appropriate solution because there will not be any other specialization going on (partial or not). – Jon Jul 11 '12 at 11:21
  • @Jon: obviously it was an humorous exagerration, but Suter makes clear point that you can avoid problems by preferring overloads over specialisations (Important Morals and Summary). In this particular example, overload version is *shorter*, for one thing. It also helps avoid problems *upfront*. Really, what's the advantage of using specialisation here? – gwiazdorrr Jul 11 '12 at 11:31
  • @gwiazdorrr: The advantage is that a non-template would not work because it would allow implicit conversions to be considered (e.g. `int` to `char`). This is exactly what we want to avoid. In effect the template tells the compiler "there's a different specialized version of this function for each type of argument", but then we don't give the specialization for any type other than `char`. Hence you will never be able to call `fn` with a non-`char` argument because the compiler will not attempt to apply conversions. – Jon Jul 11 '12 at 11:43
  • @Jon: that's exactly the same what you'd achieve with an overload. Please, [have a look](http://ideone.com/Makty). Less code, less possible complications. Again, any advantages of choosing specialisation? – gwiazdorrr Jul 11 '12 at 11:49
  • @gwiazdorrr: Sorry, I misunderstood your intent. In hindsight it's obvious that you were not objecting to the template but rather the specialization. I have edited the answer to match your suggestion. – Jon Jul 11 '12 at 12:39
9

You can use polymorphism and define several versions one accepting int and one short and throw exceptions or cause assertion failures, or hide the constructor:

struct MyInt2{
    int v;
    MyInt2(char vv){v=vv;}
    MyInt2(short s) { throw "short"; } /* exception, runtime error */
    MyInt2(){}
    private:
    MyInt2(int v) { } /* hidden, compile time error */
};
perreal
  • 94,503
  • 21
  • 155
  • 181
4

You want char to implicitly convert into MyInt2 which then to MyInt. But two level of conversion is not allowed by the language specification.

If you want to do it for one-level of implicit conversion, then C++11 can help you.

In C++11, you can delete constructor taking int, allow the one which takes char as:

struct MyInt{
    int v;

    MyInt(char vv) : v(vv) {} //use mem-initialization list

    MyInt(int) = delete;  //VALID only in C++11
};

Notice the deleted constructor which takes int, it means this:

MyInt m1(10); //error - can't accept int!

MyInt m2((char)10); //okay - can accept char!

See Online Demo

In fact, you can delete ALL constructors except one which takes char as:

struct MyInt{
    //..
    MyInt(char vv); //ALLOW
 
    template<typename T>
    MyInt(T) = delete;  //DELETED the rest!
};

Demo

Learn more about :

Hope that helps.

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • I hate to say this but really you could have easily put a comment in perreal answer saying "Alternatively you may use `MyInt(int) = delete;` in C++11 instead of making it private". +1 anyways –  Jul 11 '12 at 11:08
  • 1
    @acidzombie24: Why should I make it a comment, when it seems to be an answer for many readers who may wish to do something similar? Comments are less valuable, as not everyone reads them. And my answer contains good information. – Nawaz Jul 11 '12 at 11:12