-1

Consider the following code:

template<int complexity>
class Widget 
{
public:
    Widget(int size);
};

int main()
{
    Widget<4> widget(12); // not easily readable if definition of Widget in some faraway header
    return 0;
}

Another try using conversion to enumeration type as named parameters:

enum ComplexityInt : int {};
enum SizeInt : int {};

template<ComplexityInt complexity>
class Widget 
{
public:
    Widget(SizeInt size);
};

int main()
{
    Widget<(ComplexityInt) 4> widget((SizeInt) 12);
    return 0;
}

Is the second example completely fine C++ code without some undesirable side effects or additional costs? And what are your thoughts about it from the style and readability perspectives?

Oleksa
  • 635
  • 5
  • 15
  • Since you're just casting integers as some enum type that has no actual named values, I don't see any benefits to your second example. Neither of the examples really makes sense, as it's not clear what "Widget" even is or how these values are related to it. I'd go with the one that uses less code. – paddy Mar 30 '21 at 09:12
  • Do not use C style casts, in C++ you would do `Widget`. `what are your thoughts` this forum tries to be objective, opinion based questions are off-topic. – KamilCuk Mar 30 '21 at 09:17
  • @paddy, you could try reestimate benefits after you imagine situation when there is a lot of numeric arguments, and parameters are more meaningful, and class definition in some faraway header, and you are in constant development and changing the class definition, and you use it in other parts of a program. – Oleksa Mar 30 '21 at 09:35
  • I don't have to imagine such a situation. I have one in a large project I maintain. I have `template class Foo {};` and I have an enum with actual names. So I have `Foo`, `Foo` _etc_. You haven't made it clear why you can't use names in your example. – paddy Mar 30 '21 at 10:07
  • @KamilCuk, could you point to a paragraph where I can read about such an initialization. On cppreference.com I've only found that _"enumeration types whose underlying type is fixed can be initialized from an integer without a cast, using list initialization, if all of the following is true..."_ (since C++17). – Oleksa Mar 30 '21 at 11:00
  • Well, it's just the same as `int(1)`... I think that would be https://eel.is/c++draft/expr.type.conv#1 . https://en.cppreference.com/w/cpp/language/explicit_cast the `(2) functional cast`. – KamilCuk Mar 30 '21 at 11:00
  • @KamilCuk, then why do editors accent "without cast" in that sentence such as a cast was the only way to do that from integers previously and since C++17 we can do that also with list initialization without cast. – Oleksa Mar 30 '21 at 11:04
  • I do not understand... well, I do not feel it's accented, it's just there to remind that you can now use list initialization instead of casting. Looks clear to me. Doesn't your question has the answer? It's like: "editors accent "without cast" in that sentence, _because_ such a cast was the only way to do that from integers previously and now it's not". – KamilCuk Mar 30 '21 at 11:10
  • I was hoping for an answer, unfortunately that cast is taking all the attention. Since I'm not aware of named function parameters in c++ (I know there are some proposals), you may want to look [here](https://pdimov.github.io/blog/2020/09/07/named-parameters-in-c20/), but I'm afraid that for now the only way is putting a `/* comment */` aside the parameter – MatG Mar 30 '21 at 12:12
  • @KamilCuk, when asking I wasn't sure if it's a cast or a kind of initialization. And you suggest to use functional cast instead of explicit, but AFAIK it has similar drawbacks. Using list initialization may be a better option. – Oleksa Mar 30 '21 at 13:19

4 Answers4

1

There is 2 different/orthogonal things to consider (I take Rectangle as example):

  • Do you want to name parameters? (as Height/Width)

Named parameters add a better expressiveness at the cost of some verbosity and extra types. Those types should probably only exist to pass argument explicitly though IMO.

Transposing Rectangle would swap height and width.

  • Do you want to express a type? (as Length)

Extra types allow to be safer, at the cost of extra types, and possibly some "duplicated" functions (Length should be addable, (their product give Area), but adding Weight is also possible, duplicating the addition of their underlying type).

As const, Type might need to be propagated across the code.

  • Both?

You can :-) As the concept are orthogonal. Instead of having a Height wrapping a int/double, it wraps a Length.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

I believe those casts are undefined behavior, because those integers are outside the range of the enumeration values. From C++20 draft [expr.static.cast]/10:

If the enumeration type does not have a fixed underlying type, the value is unchanged if the original value is within the range of the enumeration values (9.7.1), and otherwise, the behavior is undefined.

I disagree that the code is unreadable without those casts. I think you're overthinking it.

With any decent editor, hovering over the class name (or ctrl-clicking to navigate to its definition) would show the definition along with the name of the complexity parameter. Same for the constructor and its size parameter.

Doing any of this casting weirdness would cause the reader to wonder what is going on, instead of just going on reading the code.

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
0

Let's for a moment ignore the point whether it is valid or not.

With an enum you might improve - even if I personally won't agree with that - the readability from the calling side.

But from the perspective of the code related to the Widget code itself those enum look wrong and confusing if those actually represent an integral with arbitrary values.

And if the values of complexity and size are limited to a certain range it might be a better idea to define a dedicated type for those.

If you want to go into a direction like your enum example I would use using instead.

using ComplexityInt = int;
using SizeInt = int;

template<ComplexityInt complexity>
class Widget 
{
public:
    Widget(SizeInt size);
};

int main()
{
    Widget<ComplexityInt(4)> widget( SizeInt(12) );
    return 0;
}
t.niese
  • 39,256
  • 9
  • 74
  • 101
  • One drawback is that you can mess up order, especially when you are in constant development and changing definition and also use this class in another part of a program. Something like this `Widget widget( ComplexityInt(12) );` – Oleksa Mar 30 '21 at 09:57
  • @oleksijp That's true. If you can use `c++20` you could define an actual class template for the needed operators. And create types for `SizeInt` and `ComplexityInt` based on them. If you actually have noticeable runtime costs (which I think shouldn't) you could use a macro for production code where you map those types directly to `int`. – t.niese Mar 30 '21 at 10:24
  • `enum` is more typed than `int`. we need a "strong_typedef" (as boost provides) here, not a simple alias. – Jarod42 Mar 30 '21 at 12:45
0

As others have already pointed out, casting is a type safety violation and should be avoided wherever possible, especially c style casts (the syntax (xxx) or xxx() is not important) because you haven't control of what the compiler is actually doing. Arguments decoration is not a valid reason to bypass the type safety of the language. Well, aside this:

Widget</*Complexity*/ 4> widget(/*Size*/ 12);

If you want some compiler support, for the int argument you could safely do this:

struct Size
{
    constexpr explicit Size(const int v) noexcept : value(v) {}
    constexpr operator int() const noexcept { return value; }
    int value;
};

template<int complexity>
class Widget 
{
public:
    Widget(Size size) {}
};

int main()
{
    //Widget<4> widget(12); // Error: 12 is not a Size
    Widget<4> widget(Size{12}); // Ok
}

The code is self explaining and I'm quite sure that this won't add overhead, the binary output will be the same. Regarding the template argument the thing is a little more delicate. Supposing that you have a finite number of possible complexities, you could do this:

enum class Complexity { min=1, mid, max, super };

template<Complexity complexity>
class Widget 
{
public:
    Widget(Size size) {}
};

int main()
{
    //Widget<4> widget(Size{12}); // Error, 4 is not a Complexity
    Widget<Complexity::super> widget(Size{12}); // Ok
}
MatG
  • 574
  • 2
  • 7
  • 19
  • @oleksa Prefer type safety over other criteria. Edited my answer to address your main question. – MatG Mar 31 '21 at 04:27