Generic type inferencer function?
Our code has any number of instances of this pattern:
template
On 18.12.2015 11:20, Nat Goodspeed wrote:
Our code has any number of instances of this pattern:
template
class SomeTemplate { SomeTemplate(const T& t, const U& u); ... }; template
SomeTemplate make_SomeTemplate(const T& t, const U& u) { return SomeTemplate (t, u); } Am I overlooking a generic boost::make<something>() of this general form?
template class CLASS_TEMPLATE, typename... ARGS> CLASS_TEMPLATE
make(ARGS && ... args) { return CLASS_TEMPLATE (std::forward<ARGS>(args)...); } If that doesn't already exist... would it be useful to add (a less naive version) somewhere?
Given the small amount of code potentially to be reused, what would be the advantage of having a generic version ? The goal of abstraction should be clarity, but more often than not, generalizing code rips off not only unnecessary details but also its meaning, making the code harder, not easier, to understand. Stefan -- ...ich hab' noch einen Koffer in Berlin...
On 18 December 2015 at 10:37, Stefan Seefeld
On 18.12.2015 11:20, Nat Goodspeed wrote:
Am I overlooking a generic boost::make<something>() of this general form?
template class CLASS_TEMPLATE, typename... ARGS> CLASS_TEMPLATE
make(ARGS && ... args) { return CLASS_TEMPLATE (std::forward<ARGS>(args)...); } If that doesn't already exist... would it be useful to add (a less naive version) somewhere?
Given the small amount of code potentially to be reused, what would be the advantage of having a generic version ? The goal of abstraction should be clarity, but more often than not, generalizing code rips off not only unnecessary details but also its meaning, making the code harder, not easier, to understand.
I disagree. The make_* functions are just noise, and I'd love to see a generic one. While return type deduction has made it somewhat easier to write these functions, a generic one would be superior. Note: you cannot do a completely generic one, because you don't know whether to deduce type parameters or non-type parameters (such as for boost::array). An even better solution is a language change such as the one proposed in P0091R0 Template parameter deduction for constructors http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html, but I don't know the status of that proposal. -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com +1-847-691-1404
On Fri, Dec 18, 2015 at 11:50 AM, Nevin Liber
On 18 December 2015 at 10:37, Stefan Seefeld
wrote:
On 18.12.2015 11:20, Nat Goodspeed wrote:
Am I overlooking a generic boost::make<something>() of this general form?
Given the small amount of code potentially to be reused, what would be the advantage of having a generic version ? The goal of abstraction should be clarity, but more often than not, generalizing code rips off not only unnecessary details but also its meaning, making the code harder, not easier, to understand.
I disagree. The make_* functions are just noise, and I'd love to see a generic one. While return type deduction has made it somewhat easier to write these functions, a generic one would be superior.
Nevin speaks truth: the make_SomeTemplate() functions in our code base are just boilerplate. More importantly, encountering make_SomeTemplate() in the code *increases* the cognitive load on the maintainer. What does make_SomeTemplate() do? Oh: it returns an instance of SomeTemplate<deduced args>. Every such call requires an indirection: the maintainer must look up make_SomeTemplate() to discover its role. If we introduce a std::make() template function, that becomes part of the maintainer's working vocabulary. S/he only needs to look it up *once*. You could reasonably argue that a consistent naming convention (make_SomeTemplate() should surely return an instance of SomeTemplate) nearly eliminates the cognitive burden. And you'd almost be right -- if we could trust that no such function ever snuck in any side effects. :-( I would counter that std::make<SomeTemplate>() is an even clearer "naming convention." Moreover, I could be pretty confident that std::make() would have no undocumented side effects. I applaud Vicente's work at https://github.com/viboes/std-make Vicente, would you be willing to float the library via Boost?
On 12/21/15 9:14 AM, Nat Goodspeed wrote:
On Fri, Dec 18, 2015 at 11:50 AM, Nevin Liber
wrote: On 18 December 2015 at 10:37, Stefan Seefeld
wrote: On 18.12.2015 11:20, Nat Goodspeed wrote:
Am I overlooking a generic boost::make<something>() of this general form?
Given the small amount of code potentially to be reused, what would be the advantage of having a generic version ? The goal of abstraction should be clarity, but more often than not, generalizing code rips off not only unnecessary details but also its meaning, making the code harder, not easier, to understand.
I disagree. The make_* functions are just noise, and I'd love to see a generic one. While return type deduction has made it somewhat easier to write these functions, a generic one would be superior.
Nevin speaks truth: the make_SomeTemplate() functions in our code base are just boilerplate.
IIRC, doesn't boost phoenix have a 'construct' function that is effectively a make<...> method. I'm sure I've used that at previous employers. Jeff
Le 24/12/2015 16:21, Jeff Flinn a écrit :
On 12/21/15 9:14 AM, Nat Goodspeed wrote:
On Fri, Dec 18, 2015 at 11:50 AM, Nevin Liber
wrote: On 18 December 2015 at 10:37, Stefan Seefeld
wrote: On 18.12.2015 11:20, Nat Goodspeed wrote:
Am I overlooking a generic boost::make<something>() of this general form?
Given the small amount of code potentially to be reused, what would be the advantage of having a generic version ? The goal of abstraction should be clarity, but more often than not, generalizing code rips off not only unnecessary details but also its meaning, making the code harder, not easier, to understand.
I disagree. The make_* functions are just noise, and I'd love to see a generic one. While return type deduction has made it somewhat easier to write these functions, a generic one would be superior.
Nevin speaks truth: the make_SomeTemplate() functions in our code base are just boilerplate.
IIRC, doesn't boost phoenix have a 'construct' function that is effectively a make<...> method. I'm sure I've used that at previous employers.
Yes, there is a construct<T> function that calls to the constructor. This is not exactly what make<> does. make<> does that by default but can be customized. Vicente
Le 21/12/2015 15:14, Nat Goodspeed a écrit :
On Fri, Dec 18, 2015 at 11:50 AM, Nevin Liber
wrote: On 18 December 2015 at 10:37, Stefan Seefeld
wrote: Am I overlooking a generic boost::make<something>() of this general form? Given the small amount of code potentially to be reused, what would be
On 18.12.2015 11:20, Nat Goodspeed wrote: the advantage of having a generic version ? The goal of abstraction should be clarity, but more often than not, generalizing code rips off not only unnecessary details but also its meaning, making the code harder, not easier, to understand. I disagree. The make_* functions are just noise, and I'd love to see a generic one. While return type deduction has made it somewhat easier to write these functions, a generic one would be superior. Nevin speaks truth: the make_SomeTemplate() functions in our code base are just boilerplate.
More importantly, encountering make_SomeTemplate() in the code *increases* the cognitive load on the maintainer. What does make_SomeTemplate() do? Oh: it returns an instance of SomeTemplate<deduced args>. Every such call requires an indirection: the maintainer must look up make_SomeTemplate() to discover its role.
If we introduce a std::make() template function, that becomes part of the maintainer's working vocabulary. S/he only needs to look it up *once*.
You could reasonably argue that a consistent naming convention (make_SomeTemplate() should surely return an instance of SomeTemplate) nearly eliminates the cognitive burden. And you'd almost be right -- if we could trust that no such function ever snuck in any side effects. :-(
I would counter that std::make<SomeTemplate>() is an even clearer "naming convention." Moreover, I could be pretty confident that std::make() would have no undocumented side effects.
I applaud Vicente's work at https://github.com/viboes/std-make Thanks. Vicente, would you be willing to float the library via Boost?
https://github.com/viboes/std-make contains more than make<TMPL>(). What exactly would you like to see in Boost? Vicente
On 12/18/2015 1:20 PM, Nat Goodspeed wrote:
Our code has any number of instances of this pattern:
template
class SomeTemplate { SomeTemplate(const T& t, const U& u); ... }; template
SomeTemplate make_SomeTemplate(const T& t, const U& u) { return SomeTemplate (t, u); } Am I overlooking a generic boost::make<something>() of this general form?
template class CLASS_TEMPLATE, typename... ARGS> CLASS_TEMPLATE
make(ARGS && ... args) { return CLASS_TEMPLATE (std::forward<ARGS>(args)...); } If that doesn't already exist... would it be useful to add (a less naive version) somewhere?
This might be relevant: https://github.com/viboes/std-make Regards, -- Agustín K-ballo Bergé.- http://talesofcpp.fusionfenix.com
Also might be relevant: Template parameter deduction for constructors http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html Benedek
Le 18/12/2015 17:44, Agustín K-ballo Bergé a écrit :
On 12/18/2015 1:20 PM, Nat Goodspeed wrote:
Our code has any number of instances of this pattern:
template
class SomeTemplate { SomeTemplate(const T& t, const U& u); ... }; template
SomeTemplate make_SomeTemplate(const T& t, const U& u) { return SomeTemplate (t, u); } Am I overlooking a generic boost::make<something>() of this general form?
template class CLASS_TEMPLATE, typename... ARGS> CLASS_TEMPLATE
make(ARGS && ... args) { return CLASS_TEMPLATE (std::forward<ARGS>(args)...); } If that doesn't already exist... would it be useful to add (a less naive version) somewhere?
This might be relevant: https://github.com/viboes/std-make
Regards, Yes, I'm working on a generic make factory
There is a draft proposal not yet finished at https://github.com/viboes/std-make/blob/master/doc/proposal/factories/DXXXX_... Best, Vicente
On 12/18/2015 05:54 PM, Vicente J. Botet Escriba wrote:
There is a draft proposal not yet finished at
https://github.com/viboes/std-make/blob/master/doc/proposal/factories/DXXXX_...
I realize that you are attempting to generalize pre-existing make factories, but I still wanted to raise the following problem because that is the main reason why I often have to avoid using make_shared. If I have a class T that always must be created as a shared_ptr (e.g. because it relies on enable_shared_from_this<T>), then I can ensure that the class can only be constructed via the factory by making the constructor of T private, and let the factory be a friend of T. This works fine if my factory is a static T::create(args...) function, but not when using make_shared<T>(args...) because in practice it uses internal helper functions that I cannot make friends of my class.
Le 25/12/2015 18:38, Bjorn Reese a écrit :
On 12/18/2015 05:54 PM, Vicente J. Botet Escriba wrote:
There is a draft proposal not yet finished at
https://github.com/viboes/std-make/blob/master/doc/proposal/factories/DXXXX_...
I realize that you are attempting to generalize pre-existing make factories, but I still wanted to raise the following problem because Yes, the make function is just a generalization. that is the main reason why I often have to avoid using make_shared.
If I have a class T that always must be created as a shared_ptr (e.g. because it relies on enable_shared_from_this<T>), then I can ensure that the class can only be constructed via the factory by making the constructor of T private, and let the factory be a friend of T. This works fine if my factory is a static T::create(args...) function, but not when using make_shared<T>(args...) because in practice it uses internal helper functions that I cannot make friends of my class.
I believe there is a place for this kind of classes and to have an associated factory. The question is how to recognize these kind of classes. My library would only help you to overload make_custom so that you can write shared_ptr<T> ptr = make<T>(args...); make_custom will replace your T::create(args...). Even if the library wouldn't help you a lot (as you need to customize), would you prefer make<T>(args...) to T::create(args...)? Maybe inheriting T from a specific class could help to identify this kind of classes and so the customization. Vicente
On December 29, 2015 4:38:34 AM EST, "Vicente J. Botet Escriba"
Le 25/12/2015 18:38, Bjorn Reese a écrit :
If I have a class T that always must be created as a shared_ptr
(e.g.
because it relies on enable_shared_from_this<T>), then I can ensure that the class can only be constructed via the factory by making the constructor of T private, and let the factory be a friend of T. This works fine if my factory is a static T::create(args...) function, but not when using make_shared<T>(args...) because in practice it uses internal helper functions that I cannot make friends of my class.
I believe there is a place for this kind of classes and to have an associated factory. The question is how to recognize these kind of classes.
My library would only help you to overload make_custom so that you can write
shared_ptr<T> ptr = make<T>(args...);
make_custom will replace your T::create(args...).
Even if the library wouldn't help you a lot (as you need to customize), would you prefer make<T>(args...) to T::create(args...)?
Maybe inheriting T from a specific class could help to identify this kind of classes and so the customization.
Can't his specialization of your customization point be a friend of his class, thereby having access to the private constructor? Does the customization point specify the return type so he can get his shared_ptr? ___ Rob (Sent from my portable computation engine)
Le 29/12/2015 17:47, Rob Stewart a écrit :
On December 29, 2015 4:38:34 AM EST, "Vicente J. Botet Escriba"
wrote: Le 25/12/2015 18:38, Bjorn Reese a écrit :
If I have a class T that always must be created as a shared_ptr (e.g. because it relies on enable_shared_from_this<T>), then I can ensure that the class can only be constructed via the factory by making the constructor of T private, and let the factory be a friend of T. This works fine if my factory is a static T::create(args...) function, but not when using make_shared<T>(args...) because in practice it uses internal helper functions that I cannot make friends of my class.
I believe there is a place for this kind of classes and to have an associated factory. The question is how to recognize these kind of classes.
My library would only help you to overload make_custom so that you can write
shared_ptr<T> ptr = make<T>(args...);
make_custom will replace your T::create(args...).
Even if the library wouldn't help you a lot (as you need to customize), would you prefer make<T>(args...) to T::create(args...)?
Maybe inheriting T from a specific class could help to identify this kind of classes and so the customization. Can't his specialization of your customization point be a friend of his class, thereby having access to the private constructor? Yes of course. Does the customization point specify the return type so he can get his shared_ptr?
Currently it is determined by the the following overloads
// make overload: requires a type with a specific underlying type,
don't deduce the underlying type from Xs
template
::type make(Xs&& ...xs) { return make_custom(meta::id<M>{}, std::forward<Xs>(xs)...); }
But it could be
// make overload: requires a type with a specific underlying type,
don't deduce the underlying type from X
template
::type make(Xs&& ...xs) { return make_custom(meta::id<M>{}, std::forward<Xs>(xs)...); }
Vicente
On 12/29/2015 10:38 AM, Vicente J. Botet Escriba wrote:
Maybe inheriting T from a specific class could help to identify this kind of classes and so the customization.
Another solution could be an overload of make_custom with expression SFINAE that calls T::make(args..) if available.
Le 30/12/2015 16:38, Bjorn Reese a écrit :
On 12/29/2015 10:38 AM, Vicente J. Botet Escriba wrote:
Maybe inheriting T from a specific class could help to identify this kind of classes and so the customization.
Another solution could be an overload of make_custom with expression SFINAE that calls T::make(args..) if available.
I like this. Vicente
On Friday, December 18, 2015 at 11:20:54 AM UTC-5, Nat Goodspeed wrote:
Our code has any number of instances of this pattern:
template
class SomeTemplate { SomeTemplate(const T& t, const U& u); ... }; template
SomeTemplate make_SomeTemplate(const T& t, const U& u) { return SomeTemplate (t, u); } Am I overlooking a generic boost::make<something>() of this general form?
This can easily be built using the `fit::construct` function from the Fit library. Plus, `by` can be used to apply prohejection to handle the different type of deductions necessary. Here's how to build `make` that decays each parameter: FIT_STATIC_FUNCTION(make_tuple) = fit::constructstd::tuple().by(fit::decay); We can use `fit::decay` which will decay and unwrap references. For `tie`, we can do our own projection `lvalue`, which really does nothing except ensures that lvalues are passed to the function: struct lvalue { template<class T> constexpr T& operator()(T& x) const { return x; } }; FIT_STATIC_FUNCTION(tie) = fit::constructstd::tuple().by(lvalue()); The `forward_as_tuple` can also be written always deducing a reference: struct reference { template<class T> constexpr T&& operator()(T&& x) const { return std::forward<T>(x); } }; FIT_STATIC_FUNCTION(forward_as_tuple) = fit::constructstd::tuple().by(reference()); Of course, this can be used to create a generic factory. The problem with a generic factory is some classes might want different ways of deducing the parameters(perhaps the class doesn't allow for references at all). There could be some metaprogramming done to notify the generic factory, however, it might be easier just to write a custom factory(its only one line of code). So it might be better for the generic factory to have a customization point instead. Also, the three different factories provided by tuple seem to be a bad choice for the more general construction. It would be better to have three factory type functions like this: make - deduces lvalues as lvalues and rvalues as rvalues(ie it contains no rvalue references) make_forward - Everything is deduced as reference(like `forward_as_tuple`) make_decay - decay the parameters and unwrap references(like `make_tuple` and `make_pair`) Of course, this changes what people generally view `make` as. Perhaps a different name could be used other than `make`. Paul FIT_STATIC_FUNCTION(forward_as_tuple) = fit::constructstd::tuple().by(reference()); Of course, this can be used to create a generic factory. The problem with a generic factory is some classes might want different ways of deducing the parameters(perhaps the class doesn't allow for references at all). There could be some metaprogramming done to notify the generic factory, however, it might be easier just to write a custom factory(its only one line of code). So it might be better for the generic factory to have a customization point instead.
Le 27/12/2015 02:54, Paul Fultz II a écrit :
On Friday, December 18, 2015 at 11:20:54 AM UTC-5, Nat Goodspeed wrote:
Our code has any number of instances of this pattern:
template
class SomeTemplate { SomeTemplate(const T& t, const U& u); ... }; template
SomeTemplate make_SomeTemplate(const T& t, const U& u) { return SomeTemplate (t, u); } Am I overlooking a generic boost::make<something>() of this general form?
This can easily be built using the `fit::construct` function from the Fit library.
Plus, `by` can be used to apply prohejection to handle the different type of deductions necessary. Here's how to build `make` that decays each parameter:
FIT_STATIC_FUNCTION(make_tuple) = fit::constructstd::tuple().by(fit::decay);
We can use `fit::decay` which will decay and unwrap references. For `tie`, we can do our own projection `lvalue`, which really does nothing except ensures that lvalues are passed to the function:
struct lvalue { template<class T> constexpr T& operator()(T& x) const { return x; } }; FIT_STATIC_FUNCTION(tie) = fit::constructstd::tuple().by(lvalue());
The `forward_as_tuple` can also be written always deducing a reference:
struct reference { template<class T> constexpr T&& operator()(T&& x) const { return std::forward<T>(x); } }; FIT_STATIC_FUNCTION(forward_as_tuple) = fit::constructstd::tuple().by(reference()); I don't know what the other think of having several ways to deduce the
There are some some specificities my make<> function has: * The template parameter can be a template class, a type constructor or a class * The function is customizable Can fit::construct<> be used like make<>? parameters.
Of course, this can be used to create a generic factory. The problem with a generic factory is some classes might want different ways of deducing the parameters(perhaps the class doesn't allow for references at all). There could be some metaprogramming done to notify the generic factory, however, it might be easier just to write a custom factory(its only one line of code). So it might be better for the generic factory to have a customization point instead.
Also, the three different factories provided by tuple seem to be a bad choice for the more general construction. It would be better to have three factory type functions like this:
make - deduces lvalues as lvalues and rvalues as rvalues(ie it contains no rvalue references) make_forward - Everything is deduced as reference(like `forward_as_tuple`) make_decay - decay the parameters and unwrap references(like `make_tuple` and `make_pair`)
Of course, this changes what people generally view `make` as. Perhaps a different name could be used other than `make`.
I people needs to make the difference I would prefer to have make: decay the parameters and unwrap references(like `make_tuple` and `make_pair`) forward_as: Everything is deduced as reference(like `forward_as_tuple`) .... Vicente
On Tuesday, December 29, 2015 at 3:56:07 AM UTC-6, Vicente J. Botet Escriba wrote:
Le 27/12/2015 02:54, Paul Fultz II a écrit :
On Friday, December 18, 2015 at 11:20:54 AM UTC-5, Nat Goodspeed wrote:
Our code has any number of instances of this pattern:
template
class SomeTemplate { SomeTemplate(const T& t, const U& u); ... }; template
SomeTemplate make_SomeTemplate(const T& t, const U& u) { return SomeTemplate (t, u); } Am I overlooking a generic boost::make<something>() of this general
form?
This can easily be built using the `fit::construct` function from the Fit library. There are some some specificities my make<> function has: * The template parameter can be a template class, a type constructor or a class
`fit::construct_meta` can be used with a metafunction or a metafunction class.
* The function is customizable
There is no need for customization. Rather `fit::construct` is used to build these functions.
Can fit::construct<> be used like make<>?
Not exactly, rather it is a different approach to polymorphic constructors. The `make<>` function tries to unify everything, and `fit::construct<>` removes boilerplate for writing polymorphic constructors.
Plus, `by` can be used to apply prohejection to handle the different type of deductions necessary. Here's how to build `make` that decays each parameter:
FIT_STATIC_FUNCTION(make_tuple) = fit::constructstd::tuple().by(fit::decay);
We can use `fit::decay` which will decay and unwrap references. For `tie`, we can do our own projection `lvalue`, which really does nothing except ensures that lvalues are passed to the function:
struct lvalue { template<class T> constexpr T& operator()(T& x) const { return x; } }; FIT_STATIC_FUNCTION(tie) = fit::constructstd::tuple().by(lvalue());
The `forward_as_tuple` can also be written always deducing a reference:
struct reference { template<class T> constexpr T&& operator()(T&& x) const { return std::forward<T>(x); } }; FIT_STATIC_FUNCTION(forward_as_tuple) = fit::constructstd::tuple().by(reference()); I don't know what the other think of having several ways to deduce the parameters.
Well its necessary in certain contexts, but it highly depends on the class. Some classes may not have been written to support references or rvalue references.
Of course, this can be used to create a generic factory. The problem with a generic factory is some classes might want different ways of deducing the parameters(perhaps the class doesn't allow for references at all). There could be some metaprogramming done to notify the generic factory, however, it might be easier just to write a custom factory(its only one line of code). So it might be better for the generic factory to have a customization point instead.
Also, the three different factories provided by tuple seem to be a bad choice for the more general construction. It would be better to have three factory type functions like this:
make - deduces lvalues as lvalues and rvalues as rvalues(ie it contains no rvalue references) make_forward - Everything is deduced as reference(like `forward_as_tuple`) make_decay - decay the parameters and unwrap references(like `make_tuple` and `make_pair`)
Of course, this changes what people generally view `make` as. Perhaps a different name could be used other than `make`.
I people needs to make the difference I would prefer to have
make: decay the parameters and unwrap references(like `make_tuple` and `make_pair`) forward_as: Everything is deduced as reference(like `forward_as_tuple`) ....
But you are missing the most important deduction which is deducing lvalues as lvalues and rvalues as rvalues. It currently is missing from standard C++, but it is very useful, and strikes the balance between mostly efficient and mostly safe. `make` is safe but not efficient, and `forward_as` is efficient but not always safe(rvalue references can easily lead to dangling references).
Vicente
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
participants (10)
-
Agustín K-ballo Bergé
-
Benedek Thaler
-
Bjorn Reese
-
Jeff Flinn
-
Nat Goodspeed
-
Nevin Liber
-
Paul Fultz II
-
Rob Stewart
-
Stefan Seefeld
-
Vicente J. Botet Escriba