Named constructors

199 views
Skip to first unread message

stefan...@torstonetech.com

unread,
Jan 12, 2017, 8:07:35 AM1/12/17
to ISO C++ Standard - Future Proposals
I find my self regularly creating static factory functions on classes to better describe how something is being constructed or to avoid having flags passed which may have some invalid permutations. Now some times this is because of poor design(Lazy/efficient) but it does make me wonder if constructers with names different to their class would be helpful.

Take this contrived example:
class Temperature
{
   
public:
       
Temperature(float k);
       
Temperature(float c, bool celsiusOrFahrenheit); //An abomination

       
float inKelvin() const;
       
float inCelsius() const;
       
float inFahrenheit() const;
   
private:
       
float kelvin;
};

auto zero = Temperature(0);
auto zeroC = Temperature(−273.15, true);
auto zeroF = Temperature(−459.67, false);

class Temperature
{
   
public:
       
struct F{};
       
struct C{};
       
struct K{};

       
Temperature(float k, K);
       
Temperature(float c, C);
       
Temperature(float f, F);

       
float inKelvin() const;
       
float inCelsius() const;
       
float inFahrenheit() const;
   
private:
       
float kelvin;
};


auto zero = Temperature(0, Temperature::K());
auto zeroC = Temperature(-273.15, Temperature::C());
auto zeroF = Temperature(−459.67, Temperature::F());

class Temperature
{
   
public:
       
static Temperature fromKelvin(float k);
       
static Temperature fromCelsius(float c);
       
static Temperature fromFahrenheight(float f);

       
float inKelvin() const;
       
float inCelsius() const;
       
float inFahrenheit() const;
   
private:
       
float kelvin;
};


auto zero = Temperature::fromKelvin(0);
auto zeroC = Temperature::fromCelsius(-273.15);
auto zeroF = Temperature::fromFahrenheight(−459.67);



First. I think most people wouldn't like the first.

Second. Helpful for meta programming and I quite like it in general but this becomes more complex the more arguments involved. (wish I came up with a better example now.).

Third. You cannot construct the temperature on the heap. But is as clear as the second. 

Would it be nice to do something like:
class Temperature
{
   
public:
        constructor fromKelvin
(float k);
        constructor fromCelsius
(float c);
        constructor fromFahrenheight
(float f);

       
float inKelvin() const;
       
float inCelsius() const;
       
float inFahrenheit() const;
   
private:
       
float kelvin;
};


auto zero_ptr = new Temperature::fromKelvin(0);
auto zeroC_ptr = new Temperature::fromCelsius(-273.15);
auto zeroF_ptr = new Temperature::fromFahrenheight(−459.67);

Please don't hold this against me but it does have some similarities to Objective-C's init methods or other languages where the argument names are used to select the call.

Thanks,
Stefan Pantos

Jonathan Müller

unread,
Jan 12, 2017, 8:14:19 AM1/12/17
to std-pr...@isocpp.org
Some sort of strong typedef facility would also help, create different types for Fahrenheit and Celsius and overload constructor directly.

Stefan Pantos

unread,
Jan 12, 2017, 8:41:16 AM1/12/17
to std-pr...@isocpp.org
Yes similar to the second example. I didn't want to list all possible ways.

I think it is considered good practice not to have flags as parameters to function(depending on context obviously). Better to have a  separate functions with a descriptive name and the appropriate parameters. Well this is similar but for constructors.

On Thu, 12 Jan 2017 at 13:14, Jonathan Müller <jonathanm...@gmail.com> wrote:
Some sort of strong typedef facility would also help, create different types for Fahrenheit and Celsius and overload constructor directly.








--


You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.


To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/HsChO8cvvVY/unsubscribe.


To unsubscribe from this group and all its topics, send an email to std-proposal...@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/CAEddoJZrxMK1jsWzO%3DMrBBXsVL_rMxW6LnsYUvGej%3DsyRDu%2Byg%40mail.gmail.com.



The contents (including attachments) of this email are strictly confidential, intended for the addressee(s) only and may be legally privileged.  If you are not the intended recipient, please contact the sender immediately and delete all copies of this email without distributing it further in any way.  Torstone Technology Limited, its affiliates and staff do not accept any liability for the contents of the message, and the sending of this message is not intended to form a contract with the recipient.  Torstone Technology Limited is registered in England and Wales with company number 07490275 and registered office at 8 Lloyd's Avenue, London EC3N 3EL, United Kingdom.

Nicol Bolas

unread,
Jan 12, 2017, 10:23:55 AM1/12/17
to ISO C++ Standard - Future Proposals, stefan...@torstonetech.com

Watch me:

new auto(Temperature::fromCelsius(-273.15));

Oh look, it's constructed on the heap now. The system allocates memory, passes it as the return value to `fromCelsius`, who calls the right constructor of `Temperature` in a prvalue that gets returned. No copying. No moving.

Behold the wonders of guaranteed elision ;)

Matt Calabrese

unread,
Jan 12, 2017, 10:39:12 AM1/12/17
to ISO C++ Standard - Future Proposals
On Thu, Jan 12, 2017 at 8:07 AM, <stefan...@torstonetech.com> wrote:
I find my self regularly creating static factory functions on classes to better describe how something is being constructed or to avoid having flags passed which may have some invalid permutations. Now some times this is because of poor design(Lazy/efficient) but it does make me wonder if constructers with names different to their class would be helpful.

I agree that the language needs named constructors, though this obviously needs a paper and the syntax probably has to undergo bikeshedding. The standard library keeps adding constructor tags to effectively get names, and we often have to make wacky SFINAE hacks to make those constructor overloads play nicely with other overloads. Named constructors more directly solve the problem (in contrast to tag types) and also makes overload sets smaller, making the code easier to reason about and to specify correctly (indirectly, it might even slightly improve compile times as well, but this is less of a concern). Guaranteed copy elision gets us close to having an equivalent of named constructors because it allows for named factory functions that return by value yet don't need the type to even be movable. That only solves some of the issues, though, and in practice, it still sometimes requires having (likely private) tagged constructor overloads. Named constructors might also be able to have a beneficial relationship with C++17 deduction guides.

I'd really like for someone to flesh out named constructors more in a paper and come back with something more tangible/formal, covering all of the details.

Nicol Bolas

unread,
Jan 12, 2017, 11:02:03 AM1/12/17
to ISO C++ Standard - Future Proposals

One problem I have with named constructors (as opposed to just static functions that return `T`) is that it doesn't solve the problem. The whole point of named constructors is that you have to provide a name to call the right function, yes?

Well, `T(...)` doesn't provide that name, so it can only call regular constructors. List-initialization can only call regular constructors, since it also has no name, just a type and parameters. Indirect initialization (`emplace`, `in_place_t` constructors, `make/allocate_shared/unique`, etc) can only call regular constructors for the same reason.

That's a lot of places where named constructors just aren't available to you. A `vector<Temperature>` must use the tagged version if you want to construct the object in-place. So those tagged versions have to be public.

Basically, the only code that can use named constructors is code that has direct knowledge of the class it is working with. Which admittedly is a decent amount of code. But the fact that you instantly lose this ability when you're dealing with a container of `T` makes it clear that these named constructors are not equal in power to regular ones.

As such, I don't see how named constructors as a language feature would be any improvement over a static function that returns `T`.

Matt Calabrese

unread,
Jan 12, 2017, 11:44:14 AM1/12/17
to ISO C++ Standard - Future Proposals
On Thu, Jan 12, 2017 at 11:02 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, January 12, 2017 at 10:39:12 AM UTC-5, Matt Calabrese wrote:
On Thu, Jan 12, 2017 at 8:07 AM, <stefan...@torstonetech.com> wrote:
I find my self regularly creating static factory functions on classes to better describe how something is being constructed or to avoid having flags passed which may have some invalid permutations. Now some times this is because of poor design(Lazy/efficient) but it does make me wonder if constructers with names different to their class would be helpful.

I agree that the language needs named constructors, though this obviously needs a paper and the syntax probably has to undergo bikeshedding. The standard library keeps adding constructor tags to effectively get names, and we often have to make wacky SFINAE hacks to make those constructor overloads play nicely with other overloads. Named constructors more directly solve the problem (in contrast to tag types) and also makes overload sets smaller, making the code easier to reason about and to specify correctly (indirectly, it might even slightly improve compile times as well, but this is less of a concern). Guaranteed copy elision gets us close to having an equivalent of named constructors because it allows for named factory functions that return by value yet don't need the type to even be movable. That only solves some of the issues, though, and in practice, it still sometimes requires having (likely private) tagged constructor overloads. Named constructors might also be able to have a beneficial relationship with C++17 deduction guides.

I'd really like for someone to flesh out named constructors more in a paper and come back with something more tangible/formal, covering all of the details.

One problem I have with named constructors (as opposed to just static functions that return `T`) is that it doesn't solve the problem. The whole point of named constructors is that you have to provide a name to call the right function, yes?

Well, `T(...)` doesn't provide that name, so it can only call regular constructors.

The reason I want the idea fleshed out is because there ideally would be syntax for the name there.
 
List-initialization can only call regular constructors, since it also has no name, just a type and parameters. Indirect initialization (`emplace`, `in_place_t` constructors, `make/allocate_shared/unique`, etc) can only call regular constructors for the same reason.

If the change were made without considering library, then I agree, but I don't think that's a roadblock. The addition of named constructors would likely have implications on how we design new/update existing library facilities and there's no reason to believe that we couldn't adjust appropriately.

Here's one example of what I mean -- previously in these forums I've described that it would have been nice to have had something more akin to a conceptified version of boost's in_place facilities rather than or in addition to what we do today for emplacement. One of the reasons I say this is because if done correctly, users could, for example, perform emplacement via the result of a function call. The benefit of this particular approach, especially with respect to *guaranteed* copy/move elision of C++17, is that it allows for zero copies/moves even if you are constructing via a named function with no directly corresponding constructor. Short of that kind of abstraction, you could certainly imagine an "emplace_with_result" that takes an Invocable and a set of arguments, where it constructs in-place with the result of the function call. With that kind of facility, also consider the possibility of being able to take the address of a named constructor (why not? think of the constructor sort of like static function that returns the constructed object). Then, all you'd have to do is pass that in as the Invocable, followed by your desired arguments. Now you can emplace with a named constructor. Regarding named constructors that are *overloaded* or are templates, there are already proposals to allow easily converting a set of overloads into a lambda that does the dispatching (and it's a less controversial proposal for the closed-set of overloads of class members). If we get something like that, then this solution even works concisely when you have such a templated or overloaded named constructor.

With such an "emplace_with_result" kind of function, the benefits of named constructors over a named function that simply returns T are the ability to directly initialize bases and members. Without the ability to do that, your named function needs to use some existing constructor (maybe private, and likely still requiring the tag/SFINAE dance behind the scenes).

My point is, these are all solvable problems, they just have broader implications if. It could end up being that these aren't changes that we end up wanting to do, but without a paper that really formally explores the options, it's hard to say.

Nicol Bolas

unread,
Jan 12, 2017, 12:05:31 PM1/12/17
to ISO C++ Standard - Future Proposals

So let's say we go through this effort. We add a lot of functions to the standard library. We add yet another language feature: lifting lambdas (which I'm all in favor of, regardless of this stuff). And probably some lip-service to list-initialization. Whatever.

And after all of that work, we get... the ability to do this:

auto t = Temperature::FromKelvin(...);

rather than this:

auto t = Temperature::FromKelvin(...);

Or the ability to do this:

v.emplace_back_with_result([]Temperature::FromKelvin, ...);

Rather than this:

v.emplace_back(..., Temperature::FromKelvin);

I'm really trying to understand how the named constructor version is an improvement, but it's just not coming to me. From the perspective of the person calling the code, named constructors don't seem to be an improvement over static members and tags.

You keep talking about "the tag/SFINAE dance" as though it were some complex and nightmarish code. But in most cases I've seen, it's pretty simple stuff. The result on the user end side is quite readable. I wouldn't even bother with a type for the case of `Temperature`; an enum is perfectly acceptable.

And whatever SFINAE gymnastics you have to do will become much more reasonable once we get concepts, right?

Matt Calabrese

unread,
Jan 12, 2017, 1:44:32 PM1/12/17
to ISO C++ Standard - Future Proposals
On Thu, Jan 12, 2017 at 12:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
So let's say we go through this effort. We add a lot of functions to the standard library. We add yet another language feature: lifting lambdas (which I'm all in favor of, regardless of this stuff). And probably some lip-service to list-initialization. Whatever.

And after all of that work, we get... the ability to do this:

auto t = Temperature::FromKelvin(...);

rather than this:

auto t = Temperature::FromKelvin(...);

Or the ability to do this:

v.emplace_back_with_result([]Temperature::FromKelvin, ...);

Rather than this:

v.emplace_back(..., Temperature::FromKelvin);

I'm really trying to understand how the named constructor version is an improvement, but it's just not coming to me. From the perspective of the person calling the code, named constructors don't seem to be an improvement over static members and tags.

The temperature class presented here isn't a particularly great example for reasons you've mentioned. The benefit comes primarily from development of the constructor/creation functions and from minimizing the amount of confusing overloads in an overload set. Forget about constructors for a moment -- imagine we lived in a hellish world where all functions were required to be called "foo" (perhaps almost as hellish as a world where everyone programmed in Java). In order to disambiguate what we were doing, we'd have to do things like make silly tag types for certain cases and write each overload in a way that was aware of all of the other overloads in order to make sure we don't have unintended ambiguities or incorrect matches. Obviously this is not the world of C++... except when in the limited scope of a type's constructors. Though we have fewer overloads to think about, it's the same kind of issue only on a smaller scale. We've gotten used to our tricks enough to get by, but they're still pretty weird tricks and are definitely not helping those who are new to the language. With respect to the standard library, these tricks tend to snowball as well, especially as we add more constructor overloads in each standard -- consider the evolution that the std::tuple constructors have gone through ( http://en.cppreference.com/w/cpp/utility/tuple/tuple ).

On Thu, Jan 12, 2017 at 12:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
You keep talking about "the tag/SFINAE dance" as though it were some complex and nightmarish code. But in most cases I've seen, it's pretty simple stuff. The result on the user end side is quite readable.

Depending on how it's taught, the user side of the problem isn't nearly as difficult as getting the implementation side correct, but that said, a nontrivial amount of time and analysis needs to be spent to specify the constraints properly for many types that have constructors like these. For user-developed types, I'm sure that either a similar amount of time is spent, or (worse) constraints are incorrect. If you don't buy the trickiness, again, check out all of the gross constraints that need to exist for tuple ( http://en.cppreference.com/w/cpp/utility/tuple/tuple ). It's tricky and I don't have much faith in myself nor even in the committee as a whole to always get things exactly "correct" 100% of the time. That's scary to me, especially when I think of everyday users who might attempt analogous things.

In a similar respect, there are a couple of ways that users, as opposed to the committee and implementors, are impacted by this, even if they themselves only *use* such constructors. For one, trying to figure out what on earth the tuple constructors do and which overloads would be picked by simply reading the documentation of those constructors is sometimes difficult for moderately experienced developers, let alone those newer to the language. I find this particularly embarrassing because a tuple isn't exactly a complicated notion to understand. As well, because it can be tricky to get these constraints correct without ambiguities and (worse) without unintended overloads being a better match, we may even specify these constraints incorrectly, in which case the user can be faced with some wacky compiler errors or unexpected run-time behavior.

On Thu, Jan 12, 2017 at 12:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
And whatever SFINAE gymnastics you have to do will become much more reasonable once we get concepts, right?

I suppose it depends on what you mean by "reasonable" there, but my view is not really. By that I mean for these *particular* kinds of cases, the code would look nicer, but the same kinds of constraints will need to be there. In this situation, the SFINAE gymnastics would be replaced by requires clauses, but we'd still need the same amount and kinds of constraints. What we might get in this case with concepts is the potential for better error messages when a user does something wrong, which is at least something, but the implementation and usage complexity remains mostly unchanged. Benefits of concepts are much greater for constraining and specializing generic code rather than for disambiguating tagged constructors and non-tagged overloads.

Anyway, again, I'm not absolutely convinced that named constructors are the best option, but I do think that they are an option that is very worthy of exploration in a paper. I think that there is a reasonable chance that we could get to a more sane place than where we are right now, but that's still somewhat of a guess without a deeper analysis.

Pantos, Stefan

unread,
Jan 12, 2017, 1:44:42 PM1/12/17
to std-pr...@isocpp.org
It hadn't occurred to me about elision would likely happen and I wasn't aware of 'Guaranteed Copy Elision' being in the standard.

In the world of 'almost always auto' the pattern auto x = T(blah); is quite common and it works quite nicely with auto x = T::FromKelvin();

Just off the top of my head, I'm not suggesting this is a good idea(gut feeling is that it isn't) but a named constructor could be an aliases to the unnamed constructor if it is uniquely identified by its arguments, explicit would in effect undo this. Temperature zero(0); and it would call FromKelvin constructor. You could even give a default constructor have a name making some things more explicit. optional<int>::null() for example, ok nullopt is better.

This makes me think about enum classes. You have a class which had several zero-argument constructors which all had different names.

class Colour
{
   public:
       constructor red();
       constructor green();
       constructor blue();

   private:
       int r, g, b;
};


Is there anything about them being constructors over static functions which would be good?
Initialisation list, noexcept? 

If people think it would be a worth while effort to write a proposal/paper to further explore the possibilities, I wouldn't mind having a go. Is anyone prepared to mentor me in such an endeavour? I don't have a massive amount of free time but I would certainly like to have a go.

Hope this isn't too rambling.



--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/HsChO8cvvVY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.



--
Torstone Technology Limited
8 Lloyd's Avenue, London, EC3N 3EL United Kingdom

Matt Calabrese

unread,
Jan 12, 2017, 1:47:43 PM1/12/17
to ISO C++ Standard - Future Proposals
On Thu, Jan 12, 2017 at 1:44 PM, Matt Calabrese <cala...@google.com> wrote:
The temperature class presented here isn't a particularly great example for reasons you've mentioned. The benefit comes primarily from development of the constructor/creation functions and from minimizing the amount of confusing overloads in an overload set. Forget about constructors for a moment -- imagine we lived in a hellish world where all functions were required to be called "foo" (perhaps almost as hellish as a world where everyone programmed in Java). In order to disambiguate what we were doing, we'd have to do things like make silly tag types for certain cases and write each overload in a way that was aware of all of the other overloads in order to make sure we don't have unintended ambiguities or incorrect matches.

Another thing to note, if we simply restricted ourselves to *always* using tags for constructors that aren't intended to be copy/move, we'd simplify our problem a bit, but that's not what we've done in the past.

Nicol Bolas

unread,
Jan 12, 2017, 2:33:12 PM1/12/17
to ISO C++ Standard - Future Proposals


On Thursday, January 12, 2017 at 1:44:32 PM UTC-5, Matt Calabrese wrote:
On Thu, Jan 12, 2017 at 12:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
So let's say we go through this effort. We add a lot of functions to the standard library. We add yet another language feature: lifting lambdas (which I'm all in favor of, regardless of this stuff). And probably some lip-service to list-initialization. Whatever.

And after all of that work, we get... the ability to do this:

auto t = Temperature::FromKelvin(...);

rather than this:

auto t = Temperature::FromKelvin(...);

Or the ability to do this:

v.emplace_back_with_result([]Temperature::FromKelvin, ...);

Rather than this:

v.emplace_back(..., Temperature::FromKelvin);

I'm really trying to understand how the named constructor version is an improvement, but it's just not coming to me. From the perspective of the person calling the code, named constructors don't seem to be an improvement over static members and tags.

The temperature class presented here isn't a particularly great example for reasons you've mentioned. The benefit comes primarily from development of the constructor/creation functions and from minimizing the amount of confusing overloads in an overload set. Forget about constructors for a moment -- imagine we lived in a hellish world where all functions were required to be called "foo" (perhaps almost as hellish as a world where everyone programmed in Java). In order to disambiguate what we were doing, we'd have to do things like make silly tag types for certain cases and write each overload in a way that was aware of all of the other overloads in order to make sure we don't have unintended ambiguities or incorrect matches.

That world would look like this:

foo(ActualFunctionName{}, params);

We would have a useless `foo`, followed by a type whose name describes the function we want to call, followed by the parameters to those overloads. Besides the unnecessary syntax, I don't see this as being particularly worse than today.

It's also not that much worse when defining new overloads:

struct ActualFunctionName{};

void foo(ActualFunctionName, Type1 param1, Type2 param2)
{...}

Yes, it's long-winded with a lot of extraneous syntax. But it's not hard. And it's not even particularly confusing for the user or the writer of the function. You've got one argument that is used to disambiguate the overload, and that's all you need. There's no complex SFINAE gymnastics being employed. It's something that beginning C++ programmers can handle just fine.

As I think about it, the world you're describing is a lot like a world where we do this for every function call:

invoke([]ActualFunctionName, params);

You have a parameter that disambiguates the call, followed by parameters that do the work.

Is that not exactly what we would have to do with `emplace_with_result`?

Obviously this is not the world of C++... except when in the limited scope of a type's constructors. Though we have fewer overloads to think about, it's the same kind of issue only on a smaller scale. We've gotten used to our tricks enough to get by, but they're still pretty weird tricks and are definitely not helping those who are new to the language. With respect to the standard library, these tricks tend to snowball as well, especially as we add more constructor overloads in each standard -- consider the evolution that the std::tuple constructors have gone through ( http://en.cppreference.com/w/cpp/utility/tuple/tuple ).

Let's say you take all of the `allocator_arg_t` and make them named constructors: `tuple::with_allocator(...)`. What have you changed? What have you improved?

If you want to do indirect initialization, you still have to use the long-winded `[]tuple::with_allocator` syntax. I fail to see how that is an improvement over `std::allocator_arg_t{}` as the first parameter.
 
On Thu, Jan 12, 2017 at 12:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
You keep talking about "the tag/SFINAE dance" as though it were some complex and nightmarish code. But in most cases I've seen, it's pretty simple stuff. The result on the user end side is quite readable.

Depending on how it's taught, the user side of the problem isn't nearly as difficult as getting the implementation side correct, but that said, a nontrivial amount of time and analysis needs to be spent to specify the constraints properly for many types that have constructors like these. For user-developed types, I'm sure that either a similar amount of time is spent, or (worse) constraints are incorrect. If you don't buy the trickiness, again, check out all of the gross constraints that need to exist for tuple ( http://en.cppreference.com/w/cpp/utility/tuple/tuple ). It's tricky and I don't have much faith in myself nor even in the committee as a whole to always get things exactly "correct" 100% of the time. That's scary to me, especially when I think of everyday users who might attempt analogous things.

Those SFINAE tricks have nothing to do with it being a single overload.

If you had a `tuple::convert` named constructor, one which performed implicit conversions like the variadic-template constructors that exist now, they would still need to use SFINAE to decide to be explicit or not, and they would still need SFINAE to not be present if the given types don't allow implicit conversions.

The complexity is not there to prevent inter-overload conflicts. It's there to make the type do what we need it to do.

In a similar respect, there are a couple of ways that users, as opposed to the committee and implementors, are impacted by this, even if they themselves only *use* such constructors. For one, trying to figure out what on earth the tuple constructors do and which overloads would be picked by simply reading the documentation of those constructors is sometimes difficult for moderately experienced developers, let alone those newer to the language.

Really? While looking at the list of constructors may be dizzying, actually using `tuple` is pretty straightforward:

using tpl = tuple<int, float, double>;

tpl a
(5, 32.f, 60.0);
tpl b
(5, 32, 60.0);
tpl c
(5, 32.f, 60);
tpl d
(5, 32, 60);

Those constructors are a complex way of saying, "We can implicitly convert from whatever you give us to the various types the tuple actually stores."

I fail to see how this is better:

using tpl = tuple<int, float, double>;

tpl a
(5, 32.f, 60.0);
tpl b
::implicit(5, 32, 60.0);
tpl c
::implicit(5, 32.f, 60);
tpl d
::implicit(5, 32, 60);

stefan...@torstonetech.com

unread,
Jan 12, 2017, 5:03:04 PM1/12/17
to ISO C++ Standard - Future Proposals
Hope this isn't stupid but I'm not familiar with this syntax.
v.emplace_back_with_result([]Temperature::FromKelvin, ...);

What is happening with the '[]Temperature::FromKelvin'
Thought it might have been a typo at first but it was used a couple of times. Thought I would look it up but I'm struggling to find it.

I'm also not convinced by my own suggestion. It is comforting that I wasn't completely alone in thinking it might be nice though.

Nicol Bolas

unread,
Jan 12, 2017, 6:39:54 PM1/12/17
to ISO C++ Standard - Future Proposals, stefan...@torstonetech.com
On Thursday, January 12, 2017 at 5:03:04 PM UTC-5, stefan...@torstonetech.com wrote:
Hope this isn't stupid but I'm not familiar with this syntax.
v.emplace_back_with_result([]Temperature::FromKelvin, ...);

What is happening with the '[]Temperature::FromKelvin'
Thought it might have been a typo at first but it was used a couple of times. Thought I would look it up but I'm struggling to find it.


It's not real C++. Or at least, not yet.

Matt and I are referring to a proposal for lifting lambda expressions. `[]Name` generates a lambda expression as if defined something like this:

[](auto &&...args) -> decltype(auto) {return Name(std::forward<decltype(args)>(args)...));}

Name can also be a typename, in which case it results in a lambda that constructs an object of that type. So in the above case, []Temperature::FromKelvin would be a lambda that forwards to that function and returns what it returns.

Bo Persson

unread,
Jan 13, 2017, 3:50:57 PM1/13/17
to std-pr...@isocpp.org
> autozeroF =Temperature::fromFahrenheight(−459.67);
>
> |
>
>
> First. I think most people wouldn't like the first.
>
> Second. Helpful for meta programming and I quite like it in general but
> this becomes more complex the more arguments involved. (wish I came up
> with a better example now.).
>
> Third. You cannot construct the temperature on the heap. But is as clear
> as the second.
>
> Would it be nice to do something like:
> |
> classTemperature
> {
> public:
> constructor fromKelvin(floatk);
> constructor fromCelsius(floatc);
> constructor fromFahrenheight(floatf);
>
> floatinKelvin()const;
> floatinCelsius()const;
> floatinFahrenheit()const;
> private:
> floatkelvin;
> };
>
>
> autozero_ptr =newTemperature::fromKelvin(0);
> autozeroC_ptr =newTemperature::fromCelsius(-273.15);
> autozeroF_ptr =newTemperature::fromFahrenheight(−459.67);
> |
>
> Please don't hold this against me but it does have some similarities to
> Objective-C's init methods or other languages where the argument names
> are used to select the call.
>

What about a pair of user defined literals

Temperature operator""_K(long double);
Temperature operator""_C(long double);


Then we can just do

auto temp = 273.15_K;


Bo Persson


Patrice Roy

unread,
Jan 13, 2017, 4:14:35 PM1/13/17
to std-pr...@isocpp.org
When I do this, I write an essentially constexpr Temperature<R> where R is some empty tag class (Celsius, Fahrenheit, Kelvin) such that

auto k = 273_K; // Temperature<Kelvin>
Temperature<Celsius> c = k; // converts from °K to °C

I find this reduces the amount of conversions to write (I use a neutral unit, in this case Celsius, and I offer pre-tag to_neutral/ from_neutral constexpr conversions through per-tag traits). I find this has some «plus»es :

  • units are explicit
  • lots of conversions can be done at compile-time
  • a Temperature<R> «knows» what its measurement unit is, which can make I/O nicer
Client code ends up being quite nice; such things as «fromCelsius()» feel as an unsafe interface to me, making the user do things the type system could do by itself. There's a way to get a fast and safe interface with C++11 code as is.







--
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.

Nicol Bolas

unread,
Jan 13, 2017, 4:24:44 PM1/13/17
to ISO C++ Standard - Future Proposals


On Friday, January 13, 2017 at 4:14:35 PM UTC-5, Patrice Roy wrote:
When I do this, I write an essentially constexpr Temperature<R> where R is some empty tag class (Celsius, Fahrenheit, Kelvin) such that

auto k = 273_K; // Temperature<Kelvin>
Temperature<Celsius> c = k; // converts from °K to °C

I find this reduces the amount of conversions to write (I use a neutral unit, in this case Celsius, and I offer pre-tag to_neutral/ from_neutral constexpr conversions through per-tag traits). I find this has some «plus»es :

  • units are explicit
  • lots of conversions can be done at compile-time
  • a Temperature<R> «knows» what its measurement unit is, which can make I/O nicer
Client code ends up being quite nice; such things as «fromCelsius()» feel as an unsafe interface to me, making the user do things the type system could do by itself. There's a way to get a fast and safe interface with C++11 code as is.

Such code also has a tendency to be viral. I can't merely take a `Temperature`; I have to express that I want a temperature in some particular unit. I don't care what units you provide the temperature in; that's the whole point of having such a type. But when I do my math, I'm going to get the temperature in a particular unit.

Why should the unit I intend to use internally be part of my function's interface?

I have an Angle type. I like the fact that you can add `Angle`s together, or multiply an `Angle` by a scalar. I like the fact that, if you want to get that value as a number, you need to ask for a specific unit. And I like the fact that the unit is not part of the type. Because the function to compute a rotation matrix doesn't care if you pass degrees or radians; it simply takes an `Angle`.

stefan...@torstonetech.com

unread,
Jan 16, 2017, 4:50:28 AM1/16/17
to ISO C++ Standard - Future Proposals
Really named constructors isn't specifically the case where your values have units which can help specify the constructor, although it can be helpful. Here is some more discussion:

(as Nicol pointed out static member functions work well for this already.)

Another example might be:
class Config
{
   
public:
     
static Config fromString(const string& xml);
     
static Config fromFile(const string& filename);
};

One aim would be to make code more readable and maintainable by default. Obviously everyone should name their variables sensibly but we know that it is one of the hardest tasks in computing and you cannot rely upon it. What made sense to one person doesn't necessarily make sense to another person in future.

Config myconfig(f);  //Is f a string with the content of the file or a path to the file?
auto myconfig = Config::fromString(f);//Much more clear, to me. I even have a good idea of what type f is.

How about this example (ok you could design this differently, maybe using polymorphism)?
class  Config
{
   
public:
     
static Config lazyFromFile(const string& filename)
     
{
         
Config result(filename, true);  // Do I know what true means here? You can make a guess but you cannot be certain.
     
}

      static Config fromFile(const string& filename)
     
{
         
Config result(filename, false);  // Do I know what false means here? You can make a guess but you cannot be certain.
     
}      //...
   
private:
     
Config(const string& filename_, bool loadLazily = false) : loaded(!loadLazily), filename(filename_)
     
{
           
if (!loadLazily)
           
{
               data
= loadDate(filename);
           
}
     
}

     
bool loaded;
     
string filename;
      map
<string, string> data;
};

// Would this be better?
class  Config
{
   
public:
     
constructor lazyFromFile(const string& filename_) : loaded(false), filename(filename_)
     
{}


      
constructor fromFile(const string& filename_) : Config::lazyFromFile(filename_)
     
{
           data = loadDate(filename);
           loaded = true;
     
}
//...
   
private:

     
bool loaded;
      
string filename;
      map
<string, string> data;
};

It could also be helpful in the case of refactoring as you can simply change the name of the constructor you are refactoring and you know that it won't compile in instance you haven't changed. In an ideal world everyone would have access to suitably powerful refactoring tools but that isn't always the case.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Nicol Bolas

unread,
Jan 16, 2017, 10:36:50 AM1/16/17
to ISO C++ Standard - Future Proposals, stefan...@torstonetech.com
On Monday, January 16, 2017 at 4:50:28 AM UTC-5, stefan...@torstonetech.com wrote:
Really named constructors isn't specifically the case where your values have units which can help specify the constructor, although it can be helpful. Here is some more discussion:

(as Nicol pointed out static member functions work well for this already.)

Another example might be:
class Config
{
   
public:
     
static Config fromString(const string& xml);
     
static Config fromFile(const string& filename);
};

One aim would be to make code more readable and maintainable by default. Obviously everyone should name their variables sensibly but we know that it is one of the hardest tasks in computing and you cannot rely upon it. What made sense to one person doesn't necessarily make sense to another person in future.

Config myconfig(f);  //Is f a string with the content of the file or a path to the file?
auto myconfig = Config::fromString(f);//Much more clear, to me. I even have a good idea of what type f is.


Or you could use `std::filesystem::path` for the "from-file" version. Admittedly, that's simply a poorly-chosen example, but you'd be surprised how many constructors can be differentiated just by judicious use of proper types. It's one of the reasons why C++ needs strong aliases so badly: so that we can give a generic type meaning without making the use of that type difficult.
 
How about this example (ok you could design this differently, maybe using polymorphism)?

Easily differentiated with an enum:

enum class ConfigLoad
{
    lazy
;
    standard
;
};

Config c(someString, ConfigLoad::lazy);

And, more to the point, we still get to do this:

std::any a(in_place_type<Config>, someString, ConfigLoad::lazy);

To do that with your named constructors, you'd have to invent a whole new indirect initialization mechanism, as others have suggested. Which also means that any user who creates a type with indirect initialization needs to know how to build this new mechanism in addition to the old one.

Finding ways to live with a single constructor namespace is ultimately preferable to using named constructors. Even if it leads you to doing something like this:

Config c(Config::FromString, someString);

I prefer this syntax over named constructors as a language feature or static members as named constructors. This way works indirectly, while neither of those features can do so with existing library stuff.
Reply all
Reply to author
Forward
0 new messages