60

I know you can use C++ keyword 'explicit' for constructors of classes to prevent an automatic conversion of type. Can you use this same command to prevent the conversion of parameters for a class method?

I have two class members, one which takes a bool as a param, the other an unsigned int. When I called the function with an int, the compiler converted the param to a bool and called the wrong method. I know eventually I'll replace the bool, but for now don't want to break the other routines as this new routine is developed.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
Superpolock
  • 3,515
  • 7
  • 30
  • 24
  • Wondered this same thing and think this would be useful syntax for certain free functions. Normally I want the reference to a derived class to implicitly yield the base class, except in the cases where undesirable slicing could happen, such as with a swap() function. Having swap(explicit Foo& lhs, explicit Foo& rhs) would be comforting. – Dwayne Robinson May 30 '14 at 01:41

8 Answers8

77

No, you can't use explicit, but you can use a templated function to catch the incorrect parameter types.

With C++11, you can declare the templated function as deleted. Here is a simple example:

#include <iostream>

struct Thing {
    void Foo(int value) {
        std::cout << "Foo: value" << std::endl;
    }

    template <typename T>
    void Foo(T value) = delete;
};

This gives the following error message if you try to call Thing::Foo with a size_t parameter:

error: use of deleted function
    ‘void Thing::Foo(T) [with T = long unsigned int]’

In pre-C++11 code, it can be accomplished using an undefined private function instead.

class ClassThatOnlyTakesBoolsAndUIntsAsArguments
{
public:
  // Assume definitions for these exist elsewhere
  void Method(bool arg1);
  void Method(unsigned int arg1);

  // Below just an example showing how to do the same thing with more arguments
  void MethodWithMoreParms(bool arg1, SomeType& arg2);
  void MethodWithMoreParms(unsigned int arg1, SomeType& arg2);

private:
  // You can leave these undefined
  template<typename T>
  void Method(T arg1);

  // Below just an example showing how to do the same thing with more arguments
  template<typename T>
  void MethodWithMoreParms(T arg1, SomeType& arg2);
};

The disadvantage is that the code and the error message are less clear in this case, so the C++11 option should be selected whenever available.

Repeat this pattern for every method that takes the bool or unsigned int. Do not provide an implementation for the templatized version of the method.

This will force the user to always explicitly call the bool or unsigned int version.

Any attempt to call Method with a type other than bool or unsigned int will fail to compile because the member is private, subject to the standard exceptions to visibility rules, of course (friend, internal calls, etc.). If something that does have access calls the private method, you will get a linker error.

Apollys supports Monica
  • 2,938
  • 1
  • 23
  • 33
Patrick Johnmeyer
  • 31,462
  • 2
  • 26
  • 24
  • But this will disable ALL automatic conversions. – Lev Oct 06 '08 at 20:35
  • 3
    Yes, it will disable ALL automatic conversions. Dan wants to "prevent the conversion of parameters for a class method", per the question text, and this method satisfies that. There are ways using template specialization that we could 'map' basic types to specific methods if so desired. – Patrick Johnmeyer Oct 06 '08 at 20:52
  • I realize this is an old answer. Is there a way to use `delete` or some other modern C++ features to accomplish the same thing without this `private` hackiness? Edit: Ahah! It works! – Apollys supports Monica Sep 20 '19 at 01:45
  • Yes, you can use `delete`, although I wouldn't call the use of `private` hacky. Just, not the latest way. This is one place StackOverflow doesn't always do well, dealing with change. How would you feel about incorporating your answer into mine, @Apollys, with sections for C++11 and pre-C++11? – Patrick Johnmeyer Sep 25 '19 at 05:27
  • Sure, would you prefer to keep your original answer on top or put the C++11 variation on top? – Apollys supports Monica Sep 25 '19 at 18:44
  • 1
    Done. I placed the C++11 solution first because it really should be the go-to option these days. – Apollys supports Monica Sep 26 '19 at 00:33
14

No. explicit prevents automatic conversion between specific classes, irrespective of context. And of course you can't do it for built-in classes.

Lev
  • 6,487
  • 6
  • 28
  • 29
9

The following is a very basic wrapper that can be used to create a strong typedef:

template <typename V, class D> 
class StrongType
{
public:
  inline explicit StrongType(V const &v)
  : m_v(v)
  {}

  inline operator V () const
  {
    return m_v;
  }

private:
  V m_v; // use V as "inner" type
};

class Tag1;
typedef StrongType<int, Tag1> Tag1Type;


void b1 (Tag1Type);

void b2 (int i)
{
  b1 (Tag1Type (i));
  b1 (i);                // Error
}

One nice feature of this approach, is that you can also distinguish between different parameters with the same type. For example you could have the following:

class WidthTag;
typedef StrongType<int, WidthTag> Width;  
class HeightTag;
typedef StrongType<int, HeightTag> Height;  

void foo (Width width, Height height);

It will be clear to the clients of 'foo' which argument is which.

Community
  • 1
  • 1
Richard Corden
  • 21,389
  • 8
  • 58
  • 85
  • Although the question is rather old ( 9 years thought ) : Every time I want to call `foo` with two Integer parameters I have to write `foo( Height ( int_1 ), Width ( int_2 ) )` which will find the explicit conversion operator through Argument Dependent Lookup / Koenig's Lookup ? – clickMe Jan 25 '17 at 19:41
  • @SebTu: No. `Height` and `Width` must be found by non ADL lookup. The expression `Height(int_1)` is an explicit type cast that results in the constructor of `Height` being called. ADL does not apply here. Therefore, to refer to `NS::Height` or `NS::Width` then we must either use a using directive/declaration or explicitly qualify the names. – Richard Corden Feb 02 '17 at 11:05
2

Something that might work for you is to use templates. The following shows the template function foo<>() being specialized for bool, unsigned int, and int. The main() function shows how the calls get resolved. Note that the calls that use a constant int that don't specify a type suffix will resolve to foo<int>(), so you'll get an error calling foo( 1) if you don't specialize on int. If this is the case, callers using a literal integer constant will have to use the "U" suffix to get the call to resolve (this might be the behavior you want).

Otherwise you'll have to specialize on int and use the "U" suffix or cast it to an unsigned int before passing it on to the unsigned int version (or maybe do an assert that the value isn't negative, if that's what you want).

#include <stdio.h>

template <typename T>
void foo( T);

template <>
void foo<bool>( bool x)
{
    printf( "foo( bool)\n");
}


template <>
void foo<unsigned int>( unsigned int x)
{
    printf( "foo( unsigned int)\n");
}


template <>
void foo<int>( int x)
{
    printf( "foo( int)\n");
}



int main () 
{
    foo( true);
    foo( false);
    foo( static_cast<unsigned int>( 0));
    foo( 0U);
    foo( 1U);
    foo( 2U);
    foo( 0);
    foo( 1);
    foo( 2);
}
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • I thought about doing it this way, too; having foo call foo to explicitly map. This is a better way if he wants to allow some conversions but not all. – Patrick Johnmeyer Oct 06 '08 at 21:23
  • Actually, this is really the same method as yours. When I quickly scanned yours shortly before posting I got thrown off by the multi-parameter prototypes and thought your technique was doing something different that I didn't fully understand. I should have read more closely. – Michael Burr Oct 06 '08 at 21:31
  • Ah -- the multi-parameter versions were supposed to be to clarify that it didn't only apply to single parm methods, and that only the argument in question had to be templatized. Perhaps they were more confusing than beneficial? I'll add a comment to clear this up. – Patrick Johnmeyer Oct 07 '08 at 03:18
  • Just because they confused me for a bit doesn't mean they were confusing. If that makes any sense. – Michael Burr Oct 07 '08 at 16:49
1

The currently accepted answer (using a private templated function) is nice, but outdated. With C++11, we can use deleted functions instead:

#include <iostream>

struct Thing {
    void Foo(int value) {
        std::cout << "Foo: value" << std::endl;
    }

    template <typename T>
    void Foo(T value) = delete;
};

int main() {
    Thing t;
    int int_value = 1;
    size_t size_t_value = 2;

    t.Foo(int_value);

    // t.Foo(size_t_value);  // fails with below error
    // error: use of deleted function
    //   ‘void Thing::Foo(T) [with T = long unsigned int]’

    return 0;
}

This conveys the intent of the source code more directly and supplies the user with a clearer error message when trying to use the function with disallowed parameter types.

Apollys supports Monica
  • 2,938
  • 1
  • 23
  • 33
  • I like the content of this but it could use some clarifying details and clean-up. Compiler C++11 support required, for example, and linking to the accepted answer permalink (since the accepted answer could be changed!). – Patrick Johnmeyer Sep 25 '19 at 05:39
0

Compiler gave "ambiguous call" warning, which will be sufficient.

I was doing TDD development and didn't realize I forgot to implement the corresponding call in the mock object.

Superpolock
  • 3,515
  • 7
  • 30
  • 24
  • You may want to consider updating the question to reflect that what you indicated was the problem (wrong method being selected) was not actually the problem. I was revisiting this for my own purposes and realized that even my solution does not necessarily address that, depending on the arguments. – Patrick Johnmeyer Apr 14 '09 at 16:27
0

bool IS an int that is limited to either 0 or 1. That is the whole concept of return 0;, it is logically the same as saying return false;(don't use this in code though).

WolfmanDragon
  • 7,851
  • 14
  • 49
  • 61
  • 1
    I believe bool is an actual type of its own, in C++. – Head Geek Oct 06 '08 at 23:08
  • Yes it is a type , but the type can hold only int values of 0 and 1. actually if I remember my C++ instructor from my collage days, 0 is false and anything else is true. This is the inner working of bool, not how it should be applied. – WolfmanDragon Oct 07 '08 at 20:35
  • Not to be a jerk, but "collage days"? Hehehe... made me chuckle. – Patrick Johnmeyer Oct 20 '08 at 13:42
  • You are right that bools can only hold the values 0 and 1; convert a bool to an int, you will get 0 or 1. "0 is false and anything else is true" applies in the reverse case; if you evaluate an int or other numeric as a bool. The concept of return 0 actually comes from C when there was no bool type. – Patrick Johnmeyer Oct 20 '08 at 13:44
  • I must still be in one, it took me until today to catch my pun. – WolfmanDragon Oct 21 '08 at 22:10
-2

You could also write an int version that calls the bool one.

aib
  • 45,516
  • 10
  • 73
  • 79