Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Template type deduction from value for class templates and value templates

230 views
Skip to first unread message

Adam Badura

unread,
Jun 9, 2017, 5:33:44 AM6/9/17
to
Let's start with a simple enumeration:

enum class color {
black,
gray,
white
};

Now I would like to have means to name individual enumeration values (convert them to strings). I started with something like:

template<color v>
constexpr char enum_value_name;

template<>
constexpr char enum_value_name<color::black>[] {"color::black"};
template<>
constexpr char enum_value_name<color::rray>[] {"color::gray"};
template<>
constexpr char enum_value_name<color::white>[] {"color::white"};

This works fine and all (g++ -Wall -Wextra -pedantic --std=c++14).

I do know this requires to provide the enumeration value as compile time template argument and would be of little use (by its own) in run-time. But firstly, it is easy to write a run-time function with switch-case to cover run-time conversion reusing enum_value_name. Secondly, it goes beyond what I’m interested here about.

The problem with this approach is that it doesn’t support generalization for other enumeration types. If I would try now to add

enum class shape {
triangle,
rectangle,
circle
};

there is no way to reuse enum_value_name for naming it.

To get around this problem I could use two template arguments:

template<typename T, T v>
constexpr char enum_value_name;

template<>
constexpr char enum_value_name<color, color::black>[] {"color::black"};
template<>
constexpr char enum_value_name<color, color::gray>[] {"color::gray"};
template<>
constexpr char enum_value_name<color, color::white>[] {"color::white"};

template<>
constexpr char enum_value_name<shape, shape::triangle>[] {"shape::triangle"};
template<>
constexpr char enum_value_name<shape, shape::rectangle>[] {"shape::rectangle"};
template<>
constexpr char enum_value_name<shape, shape::circle>[] {"shape::circle"};

But this looks much worse. There is significant repetition of information. Present also when you try to use it:

std::cout << enum_value_name<shape, shape::rectangle> << std::endl;

instead of much simpler:

std::cout << enum_value_name<shape::rectangle> << std::endl;

What seems to be missing here is auto-deduction of type based on value. The exact feature that we have with function templates

template<typename T>
unsigned int foo(T v) { return static_cast<unsigned int>(v); }

std::cout << foo(shape::rectangle) << std::endl;

So why is it not available for value templates (or type templates just the same…)? Are there technical difficulties? Syntax problems? Or just no one asked for it?

Melzzzzz

unread,
Jun 9, 2017, 5:49:21 AM6/9/17
to
On 2017-06-09, Adam Badura <adam.f...@gmail.com> wrote:
> Let's start with a simple enumeration:
>
> enum class color {
> black,
> gray,
> white
> };
>
> Now I would like to have means to name individual enumeration values
> (convert them to strings). I started with something like:
>
...
> So why is it not available for value templates (or type templates just
> the same…)? Are there technical difficulties? Syntax problems? Or just
> no one asked for it?
That's what map is for...

--
press any key to continue or any other to quit...

Alf P. Steinbach

unread,
Jun 9, 2017, 1:06:24 PM6/9/17
to
On 09-Jun-17 11:33 AM, Adam Badura wrote:
> Let's start with a simple enumeration:
>
> enum class color {
> black,
> gray,
> white
> };
>
> Now I would like to have means to name individual enumeration values
> (convert them to strings). I started with something like:
>
> template<color v> > constexpr char enum_value_name;
>
> template<>
> constexpr char enum_value_name<color::black>[] {"color::black"};
> template<>
> constexpr char enum_value_name<color::rray>[] {"color::gray"};
> template<>
> constexpr char enum_value_name<color::white>[] {"color::white"};
>
> This works fine and all (g++ -Wall -Wextra -pedantic --std=c++14).
>
> I do know this requires to provide the enumeration value as compile
> time template argument and would be of little use (by its own) in
> run-time. But firstly, it is easy to write a run-time function with
> switch-case to cover run-time conversion reusing enum_value_name.
> Secondly, it goes beyond what I’m interested here about.

Just define the function in the first place. Much easier. You can even
make it `constexpr`.


> The problem with this approach is that it doesn’t support
> generalization for other enumeration types. If I would try now to add
>
> enum class shape {
> triangle,
> rectangle,
> circle
> };
>
> there is no way to reuse enum_value_name for naming it.

That would not be a problem with a function: functions can be overloaded.


> To get around this problem I could use two template arguments:
>
> template<typename T, T v>
> constexpr char enum_value_name;
>
> template<>
> constexpr char enum_value_name<color, color::black>[] {"color::black"};
> template<>
> constexpr char enum_value_name<color, color::gray>[] {"color::gray"};
> template<>
> constexpr char enum_value_name<color, color::white>[] {"color::white"};
>
> template<>
> constexpr char enum_value_name<shape, shape::triangle>[] {"shape::triangle"};
> template<>
> constexpr char enum_value_name<shape, shape::rectangle>[] {"shape::rectangle"};
> template<>
> constexpr char enum_value_name<shape, shape::circle>[] {"shape::circle"};
>
> But this looks much worse. There is significant repetition of information. Present also when you try to use it:
>
> std::cout << enum_value_name<shape, shape::rectangle> << std::endl;
>
> instead of much simpler:
>
> std::cout << enum_value_name<shape::rectangle> << std::endl;
>
> What seems to be missing here is auto-deduction of type based on
> value. The exact feature that we have with function templates
Right. A function would be more practical.


> template<typename T>
> unsigned int foo(T v) { return static_cast<unsigned int>(v); }
>
> std::cout << foo(shape::rectangle) << std::endl;
>
> So why is it not available for value templates (or type templates
> just the same…)? Are there technical difficulties? Syntax problems?
> Or just no one asked for it?

Generally it's what you use functions for. But the case of creating a
factory function for a class just to get argument type deduction on
constructor arguments, was so common that reportedly C++17 adds support
for that. With C++17 (hopefully! and probably) a template parameter of a
class can be deduced from a constructor argument.

Anyway, the short answer is to use a function, just as you have
indicated yourself multiple times.

A longer answer is that you can easily automate the text mappings:


[File “enum_machinery.hpp”:]
---------------------------------------------------------------------
#pragma once

template< class Enum >
constexpr inline auto cstring_from( Enum const v )
-> char const*;

#define DEFINE_CSTRING_FROM_ENUM( Enum ) \
template<> \
constexpr inline auto cstring_from( Enum const v ) \
-> char const* \
{ \
constexpr char const* const enum_value_names[] = { NAMES }; \
return enum_value_names[int( v )]; \
}
---------------------------------------------------------------------

[File “define_enum.hpp”:]
---------------------------------------------------------------------
#pragma once

#if !(defined( ENUM ) && defined( NAMES ))
# error Define ENUM and NAMES before including this header.
#endif

#undef A
#define A(name) name
enum class ENUM{ NAMES }; // enum type definition

#undef A
#define A(name) #name
DEFINE_CSTRING_FROM_ENUM( ENUM ) // cstring_from specialization
#undef A

#undef NAMES
#undef ENUM
---------------------------------------------------------------------

[File “main.cpp”:]
---------------------------------------------------------------------
#include "enum_machinery.hpp"

// Usage:

#define ENUM shape
#define NAMES A(triangle), A(rectangle), A(circle)
#include "define_enum.hpp"

#include <iostream>
using namespace std;
auto main()
-> int
{ cout << cstring_from( shape::rectangle ) << endl; }
---------------------------------------------------------------------

Note that the `constexpr` stuff here requires C++14 or higher.


Cheers & hth.,

- Alf

Adam Badura

unread,
Jun 10, 2017, 4:48:18 PM6/10/17
to
> Just define the function in the first place. Much easier. You can even
> make it `constexpr`.

Sure I can! However, it is not the same (although maybe close enough).

A simple approach with function would be to have following:

constexpr inline char const* enum_value_to_string(color v)
{
switch(v)
{
case color::black: return "color::black";
case color::gray: return "color::gray";
case color::white: return "color::white";
}
return "<unexpected>";
}

First issue is that now we no longer have compilation error for missing value. At best we will get a warning. Not so bad, but I would prefer a required error rather then optional warning.

Second issue is that by doing so I'm loosing a piece of information: size of the string. sizeof(enum_value_name<color, color::black>) is expected 13. While with enum_value_to_string function I'm left with calling std::strlen.

Regarding your example it is all fine however doing it properly is no longer that simple. To do it properly you should take into account: namespaces, class enum vs. old enum, underlying type, programmer-provided values, including repeated values, and maybe even some more. This leads to complicated machinery that also results in code harder to read (now try to grep for "enum color"...). I'm not against such approaches - only showing that they are far from perfect.

Finally showing that there are alternatives (for a reminder: non-perfect) doesn't really answer my questions as their existence doesn't prevent larger flexibility for classes and values. Why not allow my_class<1> instead of my_class<int, 1>?

PS 1 Why are you consistently using "auto" for return type and then "-> <type>"? Syntactically it seems to be longer in cases here. What are the advantages?

PS 2 Why are you using "const" for non-reference function arguments? Does it provide any benefit?

Alf P. Steinbach

unread,
Jun 10, 2017, 5:18:27 PM6/10/17
to
On 10-Jun-17 10:48 PM, Adam Badura wrote:
>> Just define the function in the first place. Much easier. You can even
>> make it `constexpr`.
>
> Sure I can! However, it is not the same (although maybe close enough).
>
> A simple approach with function would be to have following:
>
> constexpr inline char const* enum_value_to_string(color v)
> {
> switch(v)
> {
> case color::black: return "color::black";
> case color::gray: return "color::gray";
> case color::white: return "color::white";
> }
> return "<unexpected>";
> }

Well, no I meant a function like the (working) example I showed,

#define DEFINE_CSTRING_FROM_ENUM( Enum ) \
template<> \
constexpr inline auto cstring_from( Enum const v ) \
-> char const* \
{ \
constexpr char const* const enum_value_names[] = { NAMES }; \
return enum_value_names[int( v )]; \
}

Arrays are nice for arrays of values.


> First issue is that now we no longer have compilation error for
> missing value.

No problem, just add that.


> At best we will get a warning. Not so bad, but I would
> prefer a required error rather then optional warning.
>
> Second issue is that by doing so I'm loosing a piece of information:
> size of the string. sizeof(enum_value_name<color, color::black>) is
> expected 13. While with enum_value_to_string function I'm left with
> calling std::strlen.

No problem, just let that function return a C++17 stringview, or
corresponding DIY class.


> Regarding your example it is all fine however doing it properly is no
> longer that simple. To do it properly you should take into account:
> namespaces, class enum vs. old enum, underlying type,
> programmer-provided values, including repeated values, and maybe even
> some more.

Sounds like premature generalization. Do you really need all that?


> This leads to complicated machinery that also results in
> code harder to read (now try to grep for "enum color"...). I'm not
> against such approaches - only showing that they are far from
> perfect.
>
> Finally showing that there are alternatives (for a reminder:
> non-perfect) doesn't really answer my questions as their existence
> doesn't prevent larger flexibility for classes and values. Why not
> allow my_class<1> instead of my_class<int, 1>?

Write up a detailed proposal. Find someone on the committee to champion
it. Might take some years. ;)



> PS 1 Why are you consistently using "auto" for return type and then
> "-> <type>"? Syntactically it seems to be longer in cases here. What
> are the advantages?

I use a single syntax for functions.

I don't see any advantage in also (arbitrarily) using the old more
limited syntax.



> PS 2 Why are you using "const" for non-reference function arguments?

Same reason as for using `const` for local variables: it constrains what
can change, so it helps with understanding and maintaining the code.


> Does it provide any benefit?

Because you ask, I think it wouldn't help to discuss the merits and
disadvantages objectively: you have likely already dismissed that. So,
authority arguments it is. That's fallacious, of course, but I don't shy
away from a few fallacies here and there if they can do good, so:

Some, /mainly C-oriented programmers/, argue that `const` has little or
no benefit.

The existence of `const` in C++, and in C, however, testifies to the
fact that some pretty competent people, language designers, thought it
was worth the effort to provide it.

Adam Badura

unread,
Jun 11, 2017, 7:40:47 PM6/11/17
to
> > Regarding your example it is all fine however doing it properly is no
> > longer that simple. To do it properly you should take into account:
> > namespaces, class enum vs. old enum, underlying type,
> > programmer-provided values, including repeated values, and maybe even
> > some more.
>
> Sounds like premature generalization. Do you really need all that?

In cases I often have to deal with I don't have control over the enum in the first place. Which dismisses your approach entirely. The enum is there, provided by a third party (or close enough) and I have to deal with it.

But it doesn't matter in the end I think. My question was not to find alternatives which I already knew quite well. It was to find out why the feature I asked for is not present. And as it seams there are no reasons deeper than just: "no on added it". Or there?

> > PS 2 Why are you using "const" for non-reference function arguments?
>
> Same reason as for using `const` for local variables: it constrains what
> can change, so it helps with understanding and maintaining the code.
>
>
> > Does it provide any benefit?
>
> Because you ask, I think it wouldn't help to discuss the merits and
> disadvantages objectively: you have likely already dismissed that. So,
> authority arguments it is. That's fallacious, of course, but I don't shy
> away from a few fallacies here and there if they can do good, so:

Well, actually I would be more than happy to know "the merits and disadvantages objectively". So I would be grateful if you could direct me to some sources or explain them yourself (although maybe a separate thread would be better for this?).

Alf P. Steinbach

unread,
Jun 11, 2017, 10:24:59 PM6/11/17
to
On 12-Jun-17 1:40 AM, Adam Badura wrote:
> [snip]
>>> PS 2 Why are you using "const" for non-reference function arguments?
>>
>> Same reason as for using `const` for local variables: it constrains what
>> can change, so it helps with understanding and maintaining the code.
>>
>>
>>> Does it provide any benefit?
>>
>> Because you ask, I think it wouldn't help to discuss the merits and
>> disadvantages objectively: you have likely already dismissed that. So,
>> authority arguments it is. That's fallacious, of course, but I don't shy
>> away from a few fallacies here and there if they can do good, so:
>
> Well, actually I would be more than happy to know "the merits and
> disadvantages objectively". So I would be grateful if you could
> direct me to some sources or explain them yourself (although maybe a
> separate thread would be better for this?).

OK.

First lets differentiate between /interface/ and /implementation/.

A top-level `const` on a formal argument is never part of a function's
interface, it doesn't matter at all to a caller, and so the C++ rules
make the following two declarations exactly equivalent, denoting the
same function, with the same function type:

void foo( int );
void foo( int const );

In particular, a header file can contain the declaration (interface)

void foo( int );

… and an implementation can then contain e.g. (implementation)

void foo( int const x ) { gurgle_snap( x ); cout << x << endl; }

… which

• is an implementation of the former declaration,

• clearly outputs the value that's passed to it, because `gurgle_snap`
can't modify `x` because `x` is `const`.

The ease with which I can conclude something about the call of the to me
unknown function `gurgle_snap`, and thereby, about the effect of
subsequent code in the function, is, in my opinion, a clear and
objective advantage of `const` on the formal argument (don't mind the
apparent inconsistency between “in my opinion” and “objective”, lest we
delve down into an Hofstadteresque infinite regress on the meaning).
I.e. it saves time for maintenance. ~80% of all coding is maintenance.

It's the same advantage as for `const` on a local variable.

And this advantage is not likely to be there if one doesn't apply
`const` by default, as one's general coding habit.

If `foo` is provided via a header-only module, then the `const` will in
practice appear in the declaration of the interface. For a potential
user of `foo` that's redundant wordage, just plain verbosity. Happily
that's what we have tools for, to produce a more pure interface
specification. E.g. that's done in Eiffel, and I think also in Java.
Unfortunately, I'm not familiar with such a tool for C++, although if
one spends a lot of money on licenses of refactoring tools (Rational
Rose comes to mind, but that's from 15 years ago) there will probably be
something like that.

So, `const` on value arguments /can/ have a slightly negative effect for
a header only module, by increasing verbosity, and hence increasing time
to read and understand the header.

Another such contextual disadvantage is that `const` prevents moving. If
a function stores an argument, or returns it, then it's desirable to be
able to move it. And this can make a huge difference.

For example, consider the following ¹contrived code to compute the
Collatz sequence recursively:

#include <vector>
#include <iostream>
#include <utility> // std::move
using namespace std;

auto is_odd( int const x ) -> bool { return x%2 == 1; }

auto concat( vector<int> v, int const x )
-> vector<int>
{
v.push_back( x );
return move( v );
}

auto collatz_aux( vector<int> numbers, int const n )
-> vector<int>
{
return (false? vector<int>{}
: n == 1? concat( move( numbers ), 1 )
: is_odd( n )? collatz_aux( concat( move( numbers ), n ),
3*n + 1 )
: collatz_aux( concat( move( numbers ), n ),
n/2 )
);
}

auto collatz( int const n )
-> vector<int>
{ return collatz_aux( {}, n ); }

auto main()
-> int
{
for( int const x : collatz( 42 ) )
{
cout << x << ' ';
}
cout << endl;
}

As given this has O(n) complexity, where n is the length of the final
sequence.

Change the `number` formal argument to `const`, and you prevent moving,
thus changing the /behavior/ to O(n^2) complexity.

That's not just some constant factor of inefficiency due to some extra
copying of data, it's one level up in the orders of inefficiency,
quadratic time. Of course, the compiler may still optimize that away.
But it would not be proper to rely on the compiler to figure that out.

So as a rule, where an argument is returned, or modified and returned,
as above, or copied to storage persisting beyond the function call, it's
generally a good idea to leave out the `const`.

But especially when the argument is returned without being modified
there is a conflict between these two ideals: being able to easily
reason about the effect and correctness of the code due to guaranteed
immutability of the argument, its `const`-ness, and supporting
efficiency, which requires some final pilfering of resources from the
argument, and hence that the argument is non-`const`.


Cheers & hth.,

- Alf

Notes:
¹ I'm indebted to Andrew Koenig for the Collatz example above, because I
invented it in response to an article he wrote about list-based
recursion, with a Collatz example, in Dr. Dobbs Journal.

Adam Badura

unread,
Jun 12, 2017, 2:50:01 AM6/12/17
to
> First lets differentiate between /interface/ and /implementation/.
>
> A top-level `const` on a formal argument is never part of a function's
> interface, it doesn't matter at all to a caller, and so the C++ rules
> make the following two declarations exactly equivalent, denoting the
> same function, with the same function type:
>
> void foo( int );
> void foo( int const );
>
> In particular, a header file can contain the declaration (interface)
>
> void foo( int );
>
> … and an implementation can then contain e.g. (implementation)
>
> void foo( int const x ) { gurgle_snap( x ); cout << x << endl; }
>
> … which
>
> • is an implementation of the former declaration,

Just to make it more clear - as I understand that when you can (declaration split from definition) you skip the const on declaration and have it on definition only? Or for the consistency you have it always?

For me the fact that const on argument is unimportant to the type was always strongly discouraging. However now when I think about it I'm unable to justify this to myself anymore.

David Brown

unread,
Jun 12, 2017, 3:04:26 AM6/12/17
to
(Please use a real newsreader and a real newsserver, not Google groups -
it makes the formatting and line wrapping much nicer.)

On 10/06/17 22:48, Adam Badura wrote:
>> Just define the function in the first place. Much easier. You can even
>> make it `constexpr`.
>
> Sure I can! However, it is not the same (although maybe close enough).
>
> A simple approach with function would be to have following:
>
> constexpr inline char const* enum_value_to_string(color v)
> {
> switch(v)
> {
> case color::black: return "color::black";
> case color::gray: return "color::gray";
> case color::white: return "color::white";
> }
> return "<unexpected>";
> }
>
> First issue is that now we no longer have compilation error for
> missing value. At best we will get a warning. Not so bad, but I would
> prefer a required error rather then optional warning.
>

If you are OK with gcc specific features, you can use:

#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Werror=switch"

constexpr inline char const* enum_value_to_string(color v)
{ ... }

#pragma GCC diagnostic pop


> Second issue is that by doing so I'm loosing a piece of information:
> size of the string. sizeof(enum_value_name<color, color::black>) is
> expected 13. While with enum_value_to_string function I'm left with
> calling std::strlen.

You have to use strlen, but gcc will calculate it at compile time, so at
least the generated code should be optimal.


Alf P. Steinbach

unread,
Jun 12, 2017, 3:50:26 AM6/12/17
to
On 12-Jun-17 8:49 AM, Adam Badura wrote:
>> First lets differentiate between /interface/ and /implementation/.
>>
>> A top-level `const` on a formal argument is never part of a function's
>> interface, it doesn't matter at all to a caller, and so the C++ rules
>> make the following two declarations exactly equivalent, denoting the
>> same function, with the same function type:
>>
>> void foo( int );
>> void foo( int const );
>>
>> In particular, a header file can contain the declaration (interface)
>>
>> void foo( int );
>>
>> … and an implementation can then contain e.g. (implementation)
>>
>> void foo( int const x ) { gurgle_snap( x ); cout << x << endl; }
>>
>> … which
>>
>> • is an implementation of the former declaration,
>
> Just to make it more clear - as I understand that when you can
> (declaration split from definition) you skip the const on declaration
> and have it on definition only? Or for the consistency you have it
> always?

My personal preference is to have top level `const`-s on definition
only, because that's where they have an advantage, while in the pure
declaration they're negative value noise.


> For me the fact that const on argument is unimportant to the type was
> always strongly discouraging. However now when I think about it I'm
> unable to justify this to myself anymore.

:)


Cheers,

- Alf

Adam Badura

unread,
Jun 12, 2017, 4:25:37 AM6/12/17
to
> If you are OK with gcc specific features, you can use:
>
> #pragma GCC diagnostic push
> #pragma GCC diagnostic error "-Werror=switch"
>
> constexpr inline char const* enum_value_to_string(color v)
> { ... }
>
> #pragma GCC diagnostic pop

A warning that is normally issued by GCC (and likely many if not most other compilers) is typically enough IMHO. Yet still vendor-agnostic error instead of it would be better.

But the warning alone is why I prefer the switch approach I showed here over array-based approaches as proposed here by Alf P. Steinbach. switch also nicely deals with enum numeric values that are not continuous starting from zero (or close enough). While I believe that if the values are such the compiler can still optimize switch into array.

> > Second issue is that by doing so I'm loosing a piece of information:
> > size of the string. sizeof(enum_value_name<color, color::black>) is
> > expected 13. While with enum_value_to_string function I'm left with
> > calling std::strlen.
>
> You have to use strlen, but gcc will calculate it at compile time, so at
> least the generated code should be optimal.

I didn't know about it. Nice! How can you be sure of this?

David Brown

unread,
Jun 12, 2017, 5:16:23 AM6/12/17
to
On 12/06/17 10:25, Adam Badura wrote:
>> If you are OK with gcc specific features, you can use:
>>
>> #pragma GCC diagnostic push
>> #pragma GCC diagnostic error "-Werror=switch"
>>
>> constexpr inline char const* enum_value_to_string(color v)
>> { ... }
>>
>> #pragma GCC diagnostic pop
>
> A warning that is normally issued by GCC (and likely many if not
> most other compilers) is typically enough IMHO. Yet still vendor-agnostic
> error instead of it would be better.

Sure, I agree here. Maybe something helpful will eventually make it
into the C++ standards as a [[ ]] attribute. But at the moment, the gcc
pragma is the best available.

>
> But the warning alone is why I prefer the switch approach I showed
> here over array-based approaches as proposed here by Alf P. Steinbach.
> switch also nicely deals with enum numeric values that are not
> continuous starting from zero (or close enough). While I believe that if
> the values are such the compiler can still optimize switch into array.
>

Yes, that's the compiler's job. gcc is good at optimising switches in a
variety of ways.

>>> Second issue is that by doing so I'm loosing a piece of information:
>>> size of the string. sizeof(enum_value_name<color, color::black>) is
>>> expected 13. While with enum_value_to_string function I'm left with
>>> calling std::strlen.
>>
>> You have to use strlen, but gcc will calculate it at compile time, so at
>> least the generated code should be optimal.
>
> I didn't know about it. Nice! How can you be sure of this?
>

Try it and see. The compiler knows how functions like strlen and strcat
work, and optimises based on that - including pre-calculating the results.

But the details can vary according to compiler switches, and perhaps
also in how the functions are used - test it a little before relying on it.


Jorgen Grahn

unread,
Jun 27, 2017, 7:19:22 AM6/27/17
to
On Mon, 2017-06-12, Adam Badura wrote:
>> First lets differentiate between /interface/ and /implementation/.
>>
>> A top-level `const` on a formal argument is never part of a function's
>> interface, it doesn't matter at all to a caller, and so the C++ rules
>> make the following two declarations exactly equivalent, denoting the
>> same function, with the same function type:
...
> Just to make it more clear - as I understand that when you can
> (declaration split from definition) you skip the const on
> declaration and have it on definition only? Or for the consistency
> you have it always?

"A foolish consistency is the hobgoblin of little minds."
I prefer to have the const only where it carries meaning:

void foo(int n);
void foo(const int n) { ... }

I think that's how most people do it.

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Jorgen Grahn

unread,
Jun 28, 2017, 10:32:34 AM6/28/17
to
On Sun, 2017-06-11, Adam Badura wrote:
...
>> > PS 2 Why are you using "const" for non-reference function arguments?
>>
[Alf]
>> Same reason as for using `const` for local variables: it constrains what
>> can change, so it helps with understanding and maintaining the code.
>>
>>
>> > Does it provide any benefit?
>>
>> Because you ask, I think it wouldn't help to discuss the merits and
>> disadvantages objectively: you have likely already dismissed that. So,
>> authority arguments it is. That's fallacious, of course, but I don't shy
>> away from a few fallacies here and there if they can do good, so:
>
> Well, actually I would be more than happy to know "the merits and
> disadvantages objectively". So I would be grateful if you could
> direct me to some sources or explain them yourself (although maybe a
> separate thread would be better for this?).

He explained it briefly above: "constrains what can change ...".

One example:

void foo(const int n)
{
assert(n != 0);
... long and complicated logic
}

You can read the rest of foo() knowing that n!=0. That becomes an
invariant. And if you accidentally try to change it, you get a
compilation error.

And if you use that idiom consistently, it means when you see

void foo(int n) { ... }

you can conclude that you probably modify n somewhere in the function,
or it would have been const.

Manfred

unread,
Jun 29, 2017, 12:48:19 PM6/29/17
to
On 6/13/2017 12:57 AM, Stefan Ram wrote:
> A hypothetical language might even allow to add the const
> qualifier of a parameters within the/body/ of the definition,
> separating the regions of the source code into a region for
> the interface and a region for the implementation:
>
> int f( int x ) /* interface */
> { const x; return 2 * x; } /* implementation */

The ancient (and still valid) C syntax comes to mind:
int f( x )
const int x;
{
return 2 * x;
}

(the syntax is valid C11, where the 'const' part only applies to modern
C, while the identifier-list syntax dates back from the beginning of time)
0 new messages