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);
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);
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.
new auto(Temperature::fromCelsius(-273.15));
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.
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.
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.
auto t = Temperature::FromKelvin(...);
auto t = Temperature::FromKelvin(...);
v.emplace_back_with_result([]Temperature::FromKelvin, ...);
v.emplace_back(..., Temperature::FromKelvin);
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.
And whatever SFINAE gymnastics you have to do will become much more reasonable once we get concepts, right?
--
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/fb643c63-11ee-4d15-a577-1321362ceb99%40isocpp.org.
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.
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.
foo(ActualFunctionName{}, params);
struct ActualFunctionName{};
void foo(ActualFunctionName, Type1 param1, Type2 param2)
{...}
invoke([]ActualFunctionName, params);
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.
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);
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);
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.
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.
[](auto &&...args) -> decltype(auto) {return Name(std::forward<decltype(args)>(args)...));}
--
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/o5bein%2476p%241%40blaine.gmane.org.
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.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 :Temperature<Celsius> c = k; // converts from °K to °CWhen I do this, I write an essentially constexpr Temperature<R> where R is some empty tag class (Celsius, Fahrenheit, Kelvin) such thatauto k = 273_K; // Temperature<Kelvin>
- 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
class Config
{
public:
static Config fromString(const string& xml);
static Config fromFile(const string& filename);
};
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.
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;
};
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.
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)?
enum class ConfigLoad
{
lazy;
standard;
};
Config c(someString, ConfigLoad::lazy);
std::any a(in_place_type<Config>, someString, ConfigLoad::lazy);
Config c(Config::FromString, someString);