21

In C++11, is there a clean way to disable implicit conversion between typedefs, or do you have to do something nasty like wrap your int in a class and define and delete various operators?

typedef int Foo;
typedef int Bar;
Foo foo(1);
Bar bar(2);
bar = foo; // Implicit conversion!
Barmar
  • 741,623
  • 53
  • 500
  • 612
Andrew Wagner
  • 22,677
  • 21
  • 86
  • 100
  • 3
    Typedefs aren't real types, they're just shorthands or aliases for the real types. – Barmar Sep 29 '15 at 15:32
  • So `foo` and `bar` are actually the same type, and there's no conversion involved. – Barmar Sep 29 '15 at 15:32
  • Yes, that is clear. But does modern C++ have a replacement that does create a new type without a bunch of boilerplate? – Andrew Wagner Sep 29 '15 at 15:33
  • @Barmar: I'd rather emphasize the difference between a thing and a *name* for the thing. Declarations (including typedef declarations) introduce *names*, not *types*. A type can have many names. – Kerrek SB Sep 29 '15 at 15:37
  • @AndrewWagner So you basically want something equivalent to the strongly-typed enum of typedefs? I'm not aware of one. – RyanP Sep 29 '15 at 15:38
  • Modern C++ does have `enum class`es, which may do what you want. – rici Sep 29 '15 at 15:39
  • Thanks! That's what I thought. But a lot of work is going on in the type system these days, so I thought it's still be fair to ask. – Andrew Wagner Sep 29 '15 at 15:39
  • This is generally called "strong typedefs;" you might find relevant info googling that term. – Angew is no longer proud of SO Sep 29 '15 at 15:47
  • `strong typedefs` is the correct term, but not specified in C++ - just for clarification. – HelloWorld Sep 29 '15 at 15:57
  • @rici: but you don't want to always cast between integers and enums. There is no implicit conversion from `int` to `enum class`. – HelloWorld Sep 29 '15 at 15:59
  • @HelloWorld: I didn't provide this as an answer precisely because I don't know what is desired by OP. But certainly one interpretation of the question is that OP is seeking a way to also block implicit conversion from `int`. – rici Sep 29 '15 at 16:25
  • Maybe this will help [How do I avoid implicit conversions on non-constructing functions?](https://stackoverflow.com/questions/12877546/how-do-i-avoid-implicit-conversions-on-non-constructing-functions). And we know that we can explicitly delete constructors in C++, can we explicitly delete the copy constructor and assign constructor? – Zhen Yang Jul 14 '21 at 01:24

4 Answers4

8

The C++ standard says:

7.1.3 The typedef specifier

A name declared with the typedef specifier becomes a typedef-name. Within the scope of its declaration, a typedef-name is syntactically equivalent to a keyword and names the type associated with the identifier in the way described in Clause 8. A typedef-name is thus a synonym for another type. A typedef-name does not introduce a new type the way a class declaration (9.1) or enum declaration does

But e.g. class or struct introduce new types. In the following example uniqueUnused does actually nothing but is used to create a different type Value<int, 1> != Value<int, 2>. So maybe this is something you are looking for. Keep in mind there is no guarantee the compiler gets rid of the outer structure! The only guarantee this code gives you it's the same size as int

template<typename T, int uniqueUnused>
struct Value
{
  Value() : _val({}) {}
  Value(T val) : _val(val) { }
  T _val;
  operator T&() { return _val; }

  // evaluate if you with or without refs for assignments
  operator T() { return _val; }
};

using Foo = Value<int, 1>;
using Bar = Value<int, 2>;
static_assert(sizeof(Foo) == sizeof(int), "int must be of same size");
static_assert(sizeof(Bar) == sizeof(int), "int must be of same size");

If you want to create a new type based on a class you can simply go with this example (this doesn't work with scalar types since you can't inherit from ints):

class Foo : public Bar // introduces a new type called Foo
{
    using Bar::Bar;
};
HelloWorld
  • 2,392
  • 3
  • 31
  • 68
8

HelloWorld explains why what you have cannot work. You'll need what's typically called a "strong" typedef to do what you want. An example implementation is BOOST_STRONG_TYPEDEF:

#include <boost/serialization/strong_typedef.hpp>    

BOOST_STRONG_TYPEDEF(int, a)
void f(int x);  // (1) function to handle simple integers
void f(a x);    // (2) special function to handle integers of type a 
int main(){
    int x = 1;
    a y;
    y = x;      // other operations permitted as a is converted as necessary
    f(x);       // chooses (1)
    f(y);       // chooses (2)
}

If we had done typedef int a;, then the code would be ambiguous.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Also a nice one. Does `BOOST_STRONG_TYPEDEF ` introduce also a wrapper or is it for no cost? – HelloWorld Sep 29 '15 at 15:52
  • @HelloWorld "`BOOST_STRONG_TYPEDEF` is a macro which generates a class named "name" which wraps an instance of its primitive type and provides appropriate conversion operators in order to make the new type substitutable for the one that it wraps." – Barry Sep 29 '15 at 15:59
  • @HelloWorld Also it **has** to introduce a wrapper, for the reasons you give in your answer :) – Barry Sep 29 '15 at 15:59
  • Thanks. I was just waiting for some magic, boost does I am not aware of :) – HelloWorld Sep 29 '15 at 16:00
1

I wanted to do something similar to keep different indexes separated not only logically, but also enforced by the compiler. The solution I came up with is basically to just define structs with one element. In some ways it's more painful to use, but it works very well with my situation since I often don't need to deal with the actual value of the index for a lot of my code, just passing it around.

typedef struct{uint16_t i;} ExpressionIndex;
typedef struct{uint16_t i;} StatementIndex;

Now, trying to do

ExpressionIndex foo() {
    StatementIndex i;
    return i;
}

yields the error error: incompatible types when returning type ‘StatementIndex’ but ‘ExpressionIndex’ was expected

Converting between types is a bit painful, but that was the intent of my change.

ExpressionIndex exp = (ExpressionIndex){stmt.i};
7thFox
  • 112
  • 10
-3

It's not strict type-checking, but illegal conversions can made visible by using original, or Apps Hungarian Notation (H. N.). If you think H. N. means name-type-as-prefix, you're wrong (it's System H. N., and it's, hm, unnecessary naming overhead).

Using the (Apps) H. N., the variable prefix marks not the type (e.g. int), but the purpose, e.g. counter, length, seconds etc. So, when you add a counter to a variable contains elapsed time, you write cntSomethingCounter + secElapsedSinceLastSomething, and you can see that it smells. Compiler does not alerts, but it pokes your eyes.

Read more: http://www.joelonsoftware.com/articles/Wrong.html

ern0
  • 3,074
  • 25
  • 40