using const to signify an expectation of a function signature from caller

68 views
Skip to first unread message

Till Heinzel

unread,
Aug 27, 2016, 4:52:42 PM8/27/16
to ISO C++ Standard - Future Proposals

I suspect this to have been proposed in some form or other before, but I couldn't find anything. It is also completely from a users perspective, as I am not an expert on implementation details. This may also already be possible in some form, in which case I apologize for my ignorance.

In short, I want to be able to classify a variable passed to a function as const, without having to define it const:

void f(int){}


int main()
{
   
int i = 0;
    f
(const i); // requires the function signature of f to be such, that i cannot be modified by it. So passed by value or const ref.
}

In this example it is pretty unnecessary, but it could be useful in certain situations.
The idea is that the interface of the called function is more explicitly specified by the caller, which could make it easier to find errors introduced by a change in the interface of a function.

some cases:
1) The most useful situation I can think of is when calling dependent functions within templates:

template<class T>
void f(T& t)
{
    X x
{};
   
auto y = t.g(x);
   
... do more stuff with x, y, t
}

In principle, it is unclear whether g modifies x or not. It could easily take a reference and change x. We would have to depend on the name of the function or check all possible T used with f to be certain.
So the modified line would be:

auto x = T.g(const x);


In this case it would implicitly constrain the possible types of T. It would also not make x a constant, so it could still be modified in the rest of the function.

2)  It could help specify overload resolution with forwarding references (note: This may be a little artificial, I have not met the problem in code).

struct Z{};

void f(const Z& z);

template<class T> void f(T&& t){...}

int main()
{
    Z z1{};
const Z z2{};
f(z1); // calls template with T = Z&
f(z2); // calls f(const Z&)
// f(const z1) // would call f(const Z&)
}

In both cases the same could be achieved with a const& alias, but that would be less easy to read and might introduce runtime overhead (possibly extra pointers?).
I think it could also be achieved with metaprogramming or concepts.
Another alternative might be to use casts to const&, although that would definitely impede readability. I'm also not completely certain about the implementation of casts, but if it works like other function-calls, this may create extra indirection through more pointers.

  • It would not introduce any new keywords, and it would not break any existing code, as const can already not be used for names. 
  • It might decrease readability if used excessively. 
  • However, it might also catch some errors. In general, it would be a way to document programmer intent in code, which would be checkable by a compiler. The alternative of using an alias might actually be a way to implement this, but then there might be runtime overhead. In principle, this should be a compile-time feature only.
  • I have not thought too much about pointers as I try to avoid raw ones, but I guess a const would imply "const* const" and a mutable the other extreme  of "*"

Going further, a similar feature might be useful for cases where we want to specify a function to be expected to modify an object. This might again be useful in template code, where we do not know the exact interface to the called function. The keyword is a little less clear, but mutable might work, also the best would be something like out, as in c#, but that would introduce a new keyword. The implication would be that the function called must be able to modify the object, which excludes pass by value or const&. 

Another direction to go is to also allow the specifier on the function call itself:

void f(T& t){
    t
.const g();
}

which might again decrease readability but increase compile-time checks. As const-correctness is supposed to relate to the external state of an object, this would express, that we do not expect the behaviour of the object t to be changed after this call. 

Again something like mutable might be useful to specify when we expect a mutator. 

All in all, I see this as mostly useful in template-code, where the exact signature of a called function might be unknown, and to increase compile time checking of expected behaviour of functions.

Patrice Roy

unread,
Aug 27, 2016, 5:29:53 PM8/27/16
to std-pr...@isocpp.org

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/25f8ca3e-9d62-4fd9-a178-a7707425b315%40isocpp.org.

Matt Calabrese

unread,
Aug 29, 2016, 2:07:19 PM8/29/16
to ISO C++ Standard - Future Proposals
On Sat, Aug 27, 2016 at 2:29 PM, Patrice Roy <patr...@gmail.com> wrote:

+1

This is the solution I also use and recommend for simple cases like this.

Notably, this is a single case of a more general problem that doesn't have a simple solution yet in the language -- properly interacting with a model of a concept in the "correct" way (in this case the concept is a Callable that takes a parameter either by value or by reference-to-const). In the very general case, you can get a similar effect from using a level of indirection of something like a traits class, which behaves like a concept_map of pre-C++11 concepts. The idea is that in the middle-man traits class you'd have your concept's associated functions with the *exact* signature and overload set that the concept may specify. All that function does internally is forward along the parameter to the underlying implementation. This way, all users of models of the concept interact with the same top-level signature (such as one that takes a reference-to-const) and they don't have to worry about what happens if their argument is, for instance cv-unqualified, or an r-value instead of a l-value, etc., since the middle-man has the proper signature and overload set. Again, though, for basic things like callbacks, as_const is the simplest solution by far.

Till Heinzel

unread,
Aug 30, 2016, 7:10:40 AM8/30/16
to ISO C++ Standard - Future Proposals, cala...@x.team
Well, yes, as_const does what I want, except for the opposite check for a non-const reference argument. I have no idea how that could be implemented with templates. With a helper template maybe:

template<typename T>
struct Ref{
    T
& ref;
}

template<typename T>
auto make_ref(T& ref){ return Ref<t>{ref};}

template<typename T>
void f(const T& t1, Ref<T> t2ref){
    T
& t2 = t2ref.ref;
   
...
}

int main()
{
    X x1
{};
    X x2
{};

    f
(as_const(x1), make_ref(x2));
}
This would make it pretty clear, but make both the signature and call to f less nice. Still, where it really matters this could work. 

The trait workaround would work for more complex cases I suppose, but I think it would result in less obvious error-messages when calling members of a template parameter. If the supplied functions signature does not match the expected, compilation should just fail and the error should be something like "No member with that signature". With the traits I would expect a less clear substitution failure, when the calling template is a class template at least. 

Anyway, I think the fundamental issue I have is that I cannot explicitly specify the complete signature I expect of the function I am calling. If the interface of the function is a contract, it is only fully enforcable from one side, not both. I realize this is not a big issue, but I do think it would make the calling code more clear. 
But seeing as the goal can be achieved without new features, it would be unnecessary. 
Reply all
Reply to author
Forward
0 new messages