84

How do I avoid implicit casting on non-constructing functions?
I have a function that takes an integer as a parameter,
but that function will also take characters, bools, and longs.
I believe it does this by implicitly casting them.
How can I avoid this so that the function only accepts parameters of a matching type, and will refuse to compile otherwise?
There is a keyword "explicit" but it does not work on non-constructing functions. :\
what do I do?

The following program compiles, although I'd like it not to:

#include <cstdlib>

//the function signature requires an int
void function(int i);

int main(){

    int i{5};
    function(i); //<- this is acceptable

    char c{'a'};
    function(c); //<- I would NOT like this to compile

    return EXIT_SUCCESS;
}

void function(int i){return;}

*please be sure to point out any misuse of terminology and assumptions

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • 2
    by the way, the ability to pass a char, long, bool, or basically any other integer type where an int is expected is because of integer promotion and conversion rules that are built into the language. This is a different mechanism than the implicit conversions done with non-explicit constructors. – Geoff Reedy Oct 13 '12 at 22:40

9 Answers9

87

Define function template which matches all other types:

void function(int); // this will be selected for int only

template <class T>
void function(T) = delete; // C++11 

This is because non-template functions with direct matching are always considered first. Then the function template with direct match are considered - so never function<int> will be used. But for anything else, like char, function<char> will be used - and this gives your compilation errrors:

void function(int) {}

template <class T>
void function(T) = delete; // C++11 


int main() {
   function(1);
   function(char(1)); // line 12
} 

ERRORS:

prog.cpp: In function 'int main()':
prog.cpp:4:6: error: deleted function 'void function(T) [with T = char]'
prog.cpp:12:20: error: used here

This is C++03 way:

// because this ugly code will give you compilation error for all other types
class DeleteOverload
{
private:
    DeleteOverload(void*);
};


template <class T>
void function(T a, DeleteOverload = 0);

void function(int a)
{}
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • 1
    for me the assertions fails even when function is called only with int parameters – Geoff Reedy Oct 13 '12 at 22:32
  • @GeoffReedy - static_assert always happens - no matter if instatianted or not. I updated my answer - now it works – PiotrNycz Oct 13 '12 at 22:44
  • 1
    Works great for me. Combined with some macro's, I made my own versions of `hton` and `ntoh` functions that will only work with the right type. – Evert Heylen Nov 10 '16 at 19:36
  • 1
    Thank you! I had a worse case, f(string&, string&) and f(bool, string&) where I was forwarding the bool call to the f(string&, string&) case, and I couldn't understand why everything was getting directed to the bool case. After reading this I realized that *everything* is convertible to bool! The template solution saved my bacon and sanity, and showed the conversion compile error that explained why my string case wasn't getting called. – Bogatyr May 18 '19 at 07:47
  • 1
    This answer is perfect. It also seems to work great with constructors as well, preventing the need for "explicit" while still preventing implicit conversions. This means that you can use `return {...}` without worrying about implicit conversions occurring. This answer is also a lot better than the SFINAE based solutions as those mess around with name mangling while this solution leaves the function signatures alone that are actually allowed. – Rian Quinn Feb 24 '20 at 16:34
27

8 years later (PRE-C++20, see edit):

The most modern solution, if you don't mind template functions -which you may mind-, is to use a templated function with std::enable_if and std::is_same.

Namely:

// Where we want to only take int
template <class T, std::enable_if_t<std::is_same_v<T,int>,bool> = false>
void func(T x) {
    
}

EDIT (c++20)

I've recently switched to c++20 and I believe that there is a better way. If your team or you don't use c++20, or are not familiar with the new concepts library, do not use this. This is much nicer and the intended method as outlines in the new c++20 standard, and by the writers of the new feature (read a papers written by Bjarne Stroustrup here.

template <class T>
    requires std::same_as(T,int)
void func(T x) {
    //...
}

Small Edit (different pattern for concepts)

The following is a much better way, because it explains your reason, to have an explicit int. If you are doing this frequently, and would like a good pattern, I would do the following:

template <class T>
concept explicit_int = std::same_as<T,int>;

template <explicit_int T>
void func(T x) {

}

Small edit 2 (the last I promise)

Also a way to accomplish this possibility:

template <class T>
concept explicit_int = std::same_as<T,int>;

void func(explicit_int auto x) {

}
Hunter Kohler
  • 1,885
  • 1
  • 18
  • 23
  • 2
    Thank you. I'm switching this to the accepted answer now. – Trevor Hickey Apr 12 '21 at 00:55
  • @TrevorHickey Check out my edit, I've been learning more about modern c++ and c++20. Concepts are a great formalization of the benefits that SFINAE give. – Hunter Kohler Apr 12 '21 at 16:42
  • Yes, concepts are good addition to your answer. I've heard it's bad practice to use concepts for constraining a single type, but it's still a valid answer here. – Trevor Hickey Apr 12 '21 at 20:15
  • @TrevorHickey I would agree; I wonder what the compiler tax is like when using it too frequently. I put another edit up there which is a much better way to use concepts in this scenario (and generally when restraining types). Interesting syntax. – Hunter Kohler Apr 13 '21 at 12:37
  • I love the [abbreviated function template](https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template) syntax usage in your "Small edit 2" example. Being able to put a bunch of concepts in a common header file and then using them with that simple syntax is great! – hadriel Oct 13 '21 at 05:01
  • I think the first code chunk should have `std::is_same_v` for C++17 (or, `std::is_same::value` for pre C++17) instead of `std::is_same`. – submartingale Nov 14 '21 at 19:32
  • @submartingale Right you are. I edited it now. – Hunter Kohler Nov 14 '21 at 19:38
26

You can't directly, because a char automatically gets promoted to int.

You can resort to a trick though: create a function that takes a char as parameter and don't implement it. It will compile, but you'll get a linker error:

void function(int i) 
{
}
void function(char i);
//or, in C++11
void function(char i) = delete;

Calling the function with a char parameter will break the build.

See http://ideone.com/2SRdM

Terminology: non-construcing functions? Do you mean a function that is not a constructor?

TankorSmash
  • 12,186
  • 6
  • 68
  • 106
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • non-constructing functions? Do you mean a function that is not a constructor? I do – Trevor Hickey Oct 13 '12 at 22:35
  • 21
    Since the OP uses C++11, we can `=delete` it and get a [compilation error](http://ideone.com/1tND3), not a linker error. – Yakov Galka Oct 13 '12 at 22:39
  • @ybungalobill c++11 solutions are good. I thought I could only delete functions that are operators inside classes though – Trevor Hickey Oct 13 '12 at 22:44
  • @LuchianGrigore yes, because of the initializer list syntax for i and c – Geoff Reedy Oct 13 '12 at 22:44
  • @ybungalobill you should add that as the answer, it's much better. – Luchian Grigore Oct 13 '12 at 22:48
  • @ybungalobill missed the link you posted . I see it now – Trevor Hickey Oct 13 '12 at 22:48
  • The OP mentioned some other types in addition to `char`. Possibly the number of undesired actual argument types is unbounded. So the general case would call for a rather large number of function declarations. The "obvious" solution there is to templatize. But then, one removes the possibility of having a general templated overload. – Cheers and hth. - Alf Oct 13 '12 at 23:38
  • There is no "automatic promotion" for function arguments, unless we are matching a C-style variadic function. In the first sentence you meant that there is an implicit conversion from `char` to `int`. – M.M Apr 03 '15 at 03:17
8

Here's a general solution that causes an error at compile time if function is called with anything but an int

template <typename T>
struct is_int { static const bool value = false; };

template <>
struct is_int<int> { static const bool value = true; };


template <typename T>
void function(T i) {
  static_assert(is_int<T>::value, "argument is not int");
  return;
}

int main() {
  int i = 5;
  char c = 'a';

  function(i);
  //function(c);

  return 0;
}

It works by allowing any type for the argument to function but using is_int as a type-level predicate. The generic implementation of is_int has a false value but the explicit specialization for the int type has value true so that the static assert guarantees that the argument has exactly type int otherwise there is a compile error.

Geoff Reedy
  • 34,891
  • 3
  • 56
  • 79
  • this is a good solution, but if I do this for a lot of functions, won't I have a lot of structs sitting around in my executable? – Trevor Hickey Oct 13 '12 at 23:02
  • No, you shouldn't. No instances of the struct are ever created, it is only used to resolve the constant `value`. – Geoff Reedy Oct 14 '12 at 17:04
2

Maybe you can use a struct to make the second function private:

#include <cstdlib>

struct NoCast {
    static void function(int i);
  private:
    static void function(char c);
};

int main(){

    int i(5);
    NoCast::function(i); //<- this is acceptable

    char c('a');
    NoCast::function(c); //<- Error

    return EXIT_SUCCESS;
}

void NoCast::function(int i){return;}

This won't compile:

prog.cpp: In function ‘int main()’:
prog.cpp:7: error: ‘static void NoCast::function(char)’ is private
prog.cpp:16: error: within this context
alestanis
  • 21,519
  • 4
  • 48
  • 67
  • a fair answer, but I'd perfer to avoid scoping – Trevor Hickey Oct 13 '12 at 22:58
  • I think this is the best answer if you just want to prevent one particular type of argument. It works for any version of C++ , involves a *lot* less typing, and it is an immediate compile time error. That said, the template answers (above) are able to hide all argument types you don't want, but they require C++11. – Mark Lakata Oct 25 '19 at 17:07
2

For C++14 (and I believe C++11), you can disable copy constructors by overloading rvalue-references as well:

Example: Say you have a base Binding<C> class, where C is either the base Constraint class, or an inherited class. Say you are storing Binding<C> by value in a vector, and you pass a reference to the binding and you wish to ensure that you do not cause an implicit copy.

You may do so by deleting func(Binding<C>&& x) (per PiotrNycz's example) for rvalue-reference specific cases.

Snippet:

template<typename T>
void overload_info(const T& x) {
  cout << "overload: " << "const " << name_trait<T>::name() << "&" << endl;
}

template<typename T>
void overload_info(T&& x) {
  cout << "overload: " << name_trait<T>::name() << "&&" << endl;
}

template<typename T>
void disable_implicit_copy(T&& x) = delete;

template<typename T>
void disable_implicit_copy(const T& x) {
  cout << "[valid] ";
  overload_info<T>(x);
}

...

int main() {
  Constraint c;
  LinearConstraint lc(1);

  Binding<Constraint> bc(&c, {});
  Binding<LinearConstraint> blc(&lc, {});

  CALL(overload_info<Binding<Constraint>>(bc));
  CALL(overload_info<Binding<LinearConstraint>>(blc));

  CALL(overload_info<Binding<Constraint>>(blc));

  CALL(disable_implicit_copy<Binding<Constraint>>(bc));
  // // Causes desired error
  // CALL(disable_implicit_copy<Binding<Constraint>>(blc));
}

Output:

>>> overload_info(bc)
overload: T&&

>>> overload_info<Binding<Constraint>>(bc)
overload: const Binding<Constraint>&

>>> overload_info<Binding<LinearConstraint>>(blc)
overload: const Binding<LinearConstraint>&

>>> overload_info<Binding<Constraint>>(blc)
implicit copy: Binding<LinearConstraint>  ->  Binding<Constraint>
overload: Binding<Constraint>&&

>>> disable_implicit_copy<Binding<Constraint>>(bc)
[valid] overload: const Binding<Constraint>&

Error (with clang-3.9 in bazel, when offending line is uncommented):

cpp_quick/prevent_implicit_conversion.cc:116:8: error: call to deleted function 'disable_implicit_copy'
  CALL(disable_implicit_copy<Binding<Constraint>>(blc));
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Full Source Code: prevent_implicit_conversion.cc

Eric Cousineau
  • 1,944
  • 14
  • 23
1

Well, I was going to answer this with the code below, but even though it works with Visual C++, in the sense of producing the desired compilation error, MinGW g++ 4.7.1 accepts it, and invokes the rvalue reference constructor!

I think it must be a compiler bug, but I could be wrong, so – anyone?

Anyway, here's the code, which may turn out to be a standard-compliant solution (or, it may turn out that that's a thinko on my part!):

#include <iostream>
#include <utility>      // std::is_same, std::enable_if
using namespace std;

template< class Type >
struct Boxed
{
    Type value;

    template< class Arg >
    Boxed(
        Arg const& v,
        typename enable_if< is_same< Type, Arg >::value, Arg >::type* = 0
        )
        : value( v )
    {
        wcout << "Generic!" << endl;
    }

    Boxed( Type&& v ): value( move( v ) )
    {
        wcout << "Rvalue!" << endl;
    }
};

void function( Boxed< int > v ) {}

int main()
{
    int i = 5;
    function( i );  //<- this is acceptable

    char c = 'a';
    function( c );  //<- I would NOT like this to compile
}
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Oh man - look at my 2 line solution: `template void function(T) = delete;`.... – PiotrNycz Oct 13 '12 at 23:27
  • @PiotrNycz: yes, I saw that - after I wrote the above code. I think it's nifty, nice, but also quite limited, since you cannot have a templated overload. The above would be a more general, reusable solution, if it should turn out to be standard compliant. – Cheers and hth. - Alf Oct 13 '12 at 23:33
  • Simple forwarding + decay on the SFINAE check does the trick AFAICT: http://coliru.stacked-crooked.com/a/ff6323ac11496981. I agree that there is no satisfactory solution (this one will not allow for reference arguments, unless using more wrappers) – sehe Mar 27 '18 at 07:40
  • 1
    This comes even close, I suppose: http://coliru.stacked-crooked.com/a/06a6be3e8f31094e – sehe Mar 27 '18 at 07:48
1

I first tried PiotrNycz's approach (for C++03, which I'm forced to use for a project), then I tried to find a more general approach and came up with this ForcedType<T> template class.

template <typename T>
struct ForcedType {
    ForcedType(T v): m_v(v) {}
    operator T&() { return m_v; }
    operator const T&() const { return m_v; }

private:
    template <typename T2>
    ForcedType(T2);

    T m_v;
};

template <typename T>
struct ForcedType<const T&> {
    ForcedType(const T& v): m_v(v) {}
    operator const T&() const { return m_v; }

private:
    template <typename T2>
    ForcedType(const T2&);

    const T& m_v;
};

template <typename T>
struct ForcedType<T&> {
    ForcedType(T& v): m_v(v) {}
    operator T&() { return m_v; }
    operator const T&() const { return m_v; }

private:
    template <typename T2>
    ForcedType(T2&);

    T& m_v;
};

If I'm not mistaken, those three specializations should cover all common use cases. I'm not sure if a specialization for rvalue-reference (on C++11 onwards) is actually needed or the by-value one suffices.

One would use it like this, in case of a function with 3 parameters whose 3rd parameter doesn't allow implicit conversions:

function(ParamType1 param1, ParamType2 param2, ForcedType<ParamType3> param3);
Fabio A.
  • 2,517
  • 26
  • 35
0

Here's a clean, simple solution in C++11 using enable_if. This example uses std::is_same for the predicate, but you can use any other constexpr yielding bool.

#include <type_traits> // type_traits has enable_if

// take will only accept double, whereas 'normal' functions
// taking double would also accept e.g. float arguments

// compile this with clang++ to get:
// $ clang++ limit*.cc
// limit_argtypes.cc:16:3: error: no matching function for call to 'take'
//   take ( 1.0f ) ;
//   ^~~~
// limit_argtypes.cc:10:6: note: candidate template ignored: requirement
// 'std::is_same<float, double>::value' was not satisfied
// [with arg_t = float]
// void take ( arg_t rhs )
//      ^
// 1 error generated.

// The error message is even clear and concise.

template < typename arg_t ,
           typename = typename std::enable_if
                      < std::is_same < arg_t , double > :: value
                      > :: type
          >
void take ( arg_t rhs )
{ }

int main ( int argc , char * argv[] )
{
  take ( 1.0 ) ;
  take ( 1.0f ) ;
}
Kay F. Jahnke
  • 371
  • 2
  • 7