Posting return_self policy implementation Nikolay '''
from return_self import * l1=Label() l1 is l1.label("bar") 1 l1 is l1.label("bar").sensitive(0) 1 l1.label("foo").sensitive(0) is l1.sensitive(1).label("bar") 1
''' def run(args = None): import sys import doctest if args is not None: sys.argv = args return doctest.testmod(sys.modules.get(__name__)) if __name__ == '__main__': print "running..." import sys sys.exit(run()[0])
Nikolay Mladenov <nickm@sitius.com> writes:
Posting return_self policy implementation
Nikolay'''
Nikolay, This is wonderful! Now, I hate to do this, but I just realized that this should really be generalized to something which takes an argument number as its parameter and returns that argument: return_identity<0> // error return_identity<>, return_identity<1> // same as return_self_policy return_identity<2> // return the 2nd argument return_identity<3> // return the 3rd argument ... etc. Don't you think that makes more sense? Would you mind making this modification? Thoughts, objections, screaming...? -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams wrote:
Nikolay Mladenov ?nickm@sitius.com? writes:
? Posting return_self policy implementation ? ? Nikolay'''
Nikolay,
This is wonderful! Now, I hate to do this, but I just realized that this should really be generalized to something which takes an argument number as its parameter and returns that argument:
I have already thought about it (I expected it from you ;-) ) and it is already there in some form: the definition of return_self_policy is template<class Base> struct return_self_policy : detail::return_arg<0, Base> {}
return_identity?0? // error return_identity??, return_identity?1? // same as return_self_policy return_identity?2? // return the 2nd argument return_identity?3? // return the 3rd argument ...
etc.
So return_arg is as your return_identity, although return_arg<0> is not an error but return_self.
Don't you think that makes more sense? Would you mind making this modification?
I agree that it makes more sense, but I am not sure how much the "more" is. Generally I don't mind.
Thoughts, objections, screaming...?
My question is: why start counting from 1? This will make the code more complicated and difficult to read.
-- Dave Abrahams Boost Consulting www.boost-consulting.com
Regards, Nikolay
Nikolay Mladenov <nickm@sitius.com> writes:
David Abrahams wrote:
Nikolay Mladenov ?nickm@sitius.com? writes:
? Posting return_self policy implementation ? ? Nikolay'''
Nikolay,
This is wonderful! Now, I hate to do this, but I just realized that this should really be generalized to something which takes an argument number as its parameter and returns that argument:
I have already thought about it (I expected it from you ;-) ) and it is already there in some form: the definition of return_self_policy is
template<class Base> struct return_self_policy : detail::return_arg<0, Base> {}
Oh, wonderful! Very shrewd of you to anticipate me.
return_identity?0? // error return_identity??, return_identity?1? // same as return_self_policy return_identity?2? // return the 2nd argument return_identity?3? // return the 3rd argument ...
etc.
So return_arg is as your return_identity, although return_arg<0> is not an error but return_self.
Well, y'see, by convention in call policies, zero is used to refer to the return value and 1 refers to the first argument. See with_custodian_and_ward.
Don't you think that makes more sense? Would you mind making this modification?
I agree that it makes more sense, but I am not sure how much the "more" is.
:-)
Generally I don't mind.
Thoughts, objections, screaming...?
My question is: why start counting from 1? This will make the code more complicated and difficult to read.
Consistency. It's a convention in CallPolicies. -- Dave Abrahams Boost Consulting www.boost-consulting.com
Posting again '''
from return_self import * l1=Label() l1 is l1.label("bar") 1 l1 is l1.label("bar").sensitive(0) 1 l1.label("foo").sensitive(0) is l1.sensitive(1).label("bar") 1 return_arg is return_arg(return_arg) 1
''' def run(args = None): import sys import doctest if args is not None: sys.argv = args return doctest.testmod(sys.modules.get(__name__)) if __name__ == '__main__': print "running..." import sys sys.exit(run()[0])
Very nice. Just a couple nits: Nikolay Mladenov <nickm@sitius.com> writes: >>> from return_self import * >>> l1=Label() >>> l1 is l1.label("bar") 1 >>> l1 is l1.label("bar").sensitive(0) 1 >>> l1.label("foo").sensitive(0) is l1.sensitive(1).label("bar") 1 >>> return_arg is return_arg(return_arg) 1 These tests will break when we get bool in Python 2.3. I use assert instead.
namespace boost { namespace python { namespace detail { template <std::size_t> struct return_arg_policy_pos_argument_must_be_greater_than_zero # if defined(__GNUC__) && __GNUC__ >= 3 || defined(__EDG__) {error} ^^^^^ This will produce an error a little bit too early ;-) I think you'd better delete it. # endif ;
Once these fixes have been made, anyone with CVS write permission can commit it as far as I'm concerned, or you can remind me when I come back from vacation. -- Dave Abrahams Boost Consulting www.boost-consulting.com
And again ... PS. How can I get you to commit the pathes I posted some time ago about keywords? David Abrahams wrote:
Very nice. Just a couple nits:
Nikolay Mladenov <nickm@sitius.com> writes:
>>> from return_self import * >>> l1=Label() >>> l1 is l1.label("bar") 1 >>> l1 is l1.label("bar").sensitive(0) 1 >>> l1.label("foo").sensitive(0) is l1.sensitive(1).label("bar") 1 >>> return_arg is return_arg(return_arg) 1
These tests will break when we get bool in Python 2.3. I use assert instead.
namespace boost { namespace python { namespace detail { template <std::size_t> struct return_arg_policy_pos_argument_must_be_greater_than_zero # if defined(__GNUC__) && __GNUC__ >= 3 || defined(__EDG__) {error} ^^^^^ This will produce an error a little bit too early ;-) I think you'd better delete it. # endif ;
Once these fixes have been made, anyone with CVS write permission can commit it as far as I'm concerned, or you can remind me when I come back from vacation.
-- Dave Abrahams Boost Consulting www.boost-consulting.com
'''
from return_self import * l1=Label() assert l1 is l1.label("bar") assert l1 is l1.label("bar").sensitive(0) assert l1.label("foo").sensitive(0) is l1.sensitive(1).label("bar") assert return_arg is return_arg(return_arg)
''' def run(args = None): import sys import doctest if args is not None: sys.argv = args return doctest.testmod(sys.modules.get(__name__)) if __name__ == '__main__': print "running..." import sys sys.exit(run()[0])
Nikolay Mladenov <nickm@sitius.com> writes:
PS. How can I get you to commit the pathes I posted some time ago about keywords?
Nikolay, If they got lost in the shuffle, I sincerely apologize. Please post a pointer to the post containing the patches. I'll try to look them over this week during my "vacation" ;-) -- Dave Abrahams Boost Consulting www.boost-consulting.com
Dave, That is what I have as diffs now. The diffs are from somewhat old cvs state (1.29.0). I've been using it for quite some time for init<> and it seems to be working nicely, but I have no tests, nor docs. If you approve this though, I will write docs and tests. Thanks, Nikolay David Abrahams wrote:
Nikolay Mladenov <nickm@sitius.com> writes:
PS. How can I get you to commit the pathes I posted some time ago about keywords?
Nikolay,
If they got lost in the shuffle, I sincerely apologize. Please post a pointer to the post containing the patches. I'll try to look them over this week during my "vacation" ;-)
-- Dave Abrahams Boost Consulting www.boost-consulting.com
44a45,51
keywords<size+1> operator , (const keywords<1> &k) const { python::detail::keywords<size+1> res; std::copy(elements, elements+size, res.elements); res.elements[size] = k.elements[0]; return res; }
46a54,56
98c108,120 < # define BOOST_PYTHON_ASSIGN_NAME(z, n, _) result.elements[n].name = name##n; ---
struct arg : detail::keywords<1> { template <class T> arg &operator = (T const &value) { elements[0].default_value = handle<>(python::borrowed(object(value).ptr())); return *this; } arg (char const *name){elements[0].name = name;} operator detail::keyword const &()const {return elements[0];} };
# define BOOST_PYTHON_ASSIGN_NAME(z, n, _) result.elements[n] = kwd##n; 100c122 < inline detail::keywords<n> args(BOOST_PP_ENUM_PARAMS_Z(1, n, char const* name)) \
inline detail::keywords<n> args(BOOST_PP_ENUM_PARAMS_Z(1, n, detail::keyword const& kwd)) \
21a22
keyword(char const* n=0):name(n){}
57a58
unsigned m_nkeyword_values;
35a36
, m_nkeyword_values(0)
42d42 < 48a49,50
object tpl (handle<>(PyTuple_New(names_and_defaults[i].default_value?2:1)));
50,51c52,53 < m_arg_names.ptr() < , i + keyword_offset ---
tpl.ptr() , 0
55a58,72
if(names_and_defaults[i].default_value){ PyTuple_SET_ITEM( tpl.ptr() , 1 , incref(names_and_defaults[i].default_value.get()) ); ++m_nkeyword_values; }
PyTuple_SET_ITEM( m_arg_names.ptr() , i + keyword_offset , incref(tpl.ptr()) );
82c99 < if (total_args >= f->m_min_arity && total_args <= f->m_max_arity) ---
if (total_args+f->m_nkeyword_values >= f->m_min_arity && total_args <= f->m_max_arity)
85c102 < if (nkeywords > 0) ---
if (nkeywords > 0 || total_args < f->m_min_arity)
94,95c111,112 < // build a new arg tuple < args2 = handle<>(PyTuple_New(total_args)); ---
// build a new arg tuple, will adjust its size later args2 = handle<>(PyTuple_New(f->m_max_arity));
102c119,120 < for (std::size_t j = nargs; j < total_args; ++j) ---
std::size_t j = nargs, k = nargs, size=PyTuple_GET_SIZE(f->m_arg_names.ptr()); for (; j < f->m_max_arity && j<size ; ++j)
104,105c122,125 < PyObject* value = PyDict_GetItem( < keywords, PyTuple_GET_ITEM(f->m_arg_names.ptr(), j)); ---
PyObject* kwd=PyTuple_GET_ITEM(f->m_arg_names.ptr(), j);
PyObject* value = nkeywords?PyDict_GetItem( keywords, PyTuple_GET_ITEM(kwd, 0)) : 0;
108a129,132
if(PyTuple_GET_SIZE(kwd)>1) value = PyTuple_GET_ITEM(kwd, 1); if (!value) {
112a137
}else ++k;
114a140,156
if(args2.get()){ //check if we proccessed all the arguments if(k < total_args) args2 = handle<>();
//adjust the parameter tuple size if(j<f->m_max_arity){ handle<> args3( PyTuple_New(j) ); for(size_t l=0; l!=j; ++l) { PyTuple_SET_ITEM(args3.get(), l, PyTuple_GET_ITEM(args3.get(), l)); PyTuple_SET_ITEM(args2.get(), l, 0); } args2 = args3; } }
Nikolay Mladenov <nickm@sitius.com> writes:
Dave,
That is what I have as diffs now. The diffs are from somewhat old cvs state (1.29.0). I've been using it for quite some time for init<> and it seems to be working nicely, but I have no tests, nor docs. If you approve this though, I will write docs and tests.
I need to see some examples/description to know what it does, I think. IIRC it has something to do with defaulted keyword arguments? If it's what I remember, I'd be very enthusiastic about adding it to the library! Thanks, Dave Nit: the coding style should be made consistent with the rest of the library. These don't really fit:
std::size_t j = nargs, k = nargs, size=PyTuple_GET_SIZE(f->m_arg_names.ptr());
}else ++k;
-- Dave Abrahams Boost Consulting www.boost-consulting.com
This is how I use it: /////the.cpp//// python::class_<FileDialog, FileDialogPyX, boost::noncopyable> ("FileDialog", python::no_init) .def(python::init<const char * , const char * , const char * , const char * , const char * >(python::args( python::arg("title") = (const char *)0 , python::arg("prompt") = (const char *)0 , python::arg("extension") = (const char *)0 , python::arg("directory")= (const char *)0 , python::arg("file")= (const char *)0 )) ) .def(python::init<python::optional< const char * , const char * , const char * , const char * , const char * > >() ) /////the.py//// fd1 = FileDialog() fd2 = FileDialog("Title") fd2 = FileDialog(title="Title") fd2 = FileDialog("Title", extension='.py', prompt='Select your script') David Abrahams wrote:
Nikolay Mladenov <nickm@sitius.com> writes:
Dave,
That is what I have as diffs now. The diffs are from somewhat old cvs state (1.29.0). I've been using it for quite some time for init<> and it seems to be working nicely, but I have no tests, nor docs. If you approve this though, I will write docs and tests.
I need to see some examples/description to know what it does, I think. IIRC it has something to do with defaulted keyword arguments? If it's what I remember, I'd be very enthusiastic about adding it to the library!
Thanks, Dave
Nit: the coding style should be made consistent with the rest of the library. These don't really fit:
std::size_t j = nargs, k = nargs, size=PyTuple_GET_SIZE(f->m_arg_names.ptr());
}else ++k;
I will look into this. If you mean that some new line are missing, consider it fixed.
-- Dave Abrahams Boost Consulting www.boost-consulting.com
Nikolay Mladenov <nickm@sitius.com> writes:
This is how I use it:
Reformatting to avoid "endline layout" (google for it): /////the.cpp//// class_<FileDialog, FileDialogPyX, boost::noncopyable>( "FileDialog", python::no_init ) .def( init< const char * , const char * , const char * , const char * , const char * >( args( arg("title") = (const char *)0 , arg("prompt") = (const char *)0 , arg("extension") = (const char *)0 , arg("directory") = (const char *)0 , arg("file") = (const char *)0 ) ) ) .def( init< optional< const char * , const char * , const char * , const char * , const char * > >() ) /////the.py//// fd1 = FileDialog() fd2 = FileDialog("Title") fd2 = FileDialog(title="Title") fd2 = FileDialog("Title", extension='.py', prompt='Select your script') OK, I don't understand this. Isn't the 2nd constructor redundant? Why are you using no_init? Doesn't this stuff work for regular functions and member functions, too? ...and shouldn't we get rid of the need to write the outer "args(...)"? I suggest you write the documentation which would explain all this, but posting informally is fine if you try to ensure that I don't have to ask you lots more questions in order to understand it ;-)
Nit: the coding style should be made consistent with the rest of the library. These don't really fit:
std::size_t j = nargs, k = nargs, size=PyTuple_GET_SIZE(f->m_arg_names.ptr());
}else ++k;
I will look into this. If you mean that some new line are missing, consider it fixed.
Also that you used commas between variable declarations, K&R braces within functions, no spaces around '?', '<', '>', ... -- Dave Abrahams Boost Consulting www.boost-consulting.com
I've attached test files.
OK, I don't understand this. Isn't the 2nd constructor redundant? Yes it is but, having it makes certain calls more efficient (it's cheating, in a way ), see the commented section in the py-file, may be def with args can be made so it is really redundant. Why are you using no_init? For the same reason, not that it makes sence for my first example, but in general, I want my most offsen called overload to be first in the list - so defined last.
Doesn't this stuff work for regular functions and member functions, too? Yes it works, see the test.
...and shouldn't we get rid of the need to write the outer "args(...)"? It is not necessary to have it, you can write (arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) instead of args(arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) but you'll probably want args("a","b","c", arg("n") = std::string())) instead of (arg("a"), arg("b"), arg("c"), arg("n") = std::string()))
I suggest you write the documentation which would explain all this,
Sure, but I think I'll wait a bit to see all of your comments:)
but posting informally is fine if you try to ensure that I don't have to ask you lots more questions in order to understand it ;-)
I hope this all makes sense to you. Regards
Nit: the coding style should be made consistent with the rest of the library. These don't really fit:
std::size_t j = nargs, k = nargs, size=PyTuple_GET_SIZE(f->m_arg_names.ptr());
}else ++k;
I will look into this. If you mean that some new line are missing, consider it fixed.
Also that you used commas between variable declarations, K&R braces within functions, no spaces around '?', '<', '>', ...
-- Dave Abrahams Boost Consulting www.boost-consulting.com
'''
from keywords import * f = Foo() b = Bar() f.a(), f.b(), f.n() (0, 0.0, '') b.a(), b.b(), b.n() (0, 0.0, '') f = Foo(1) b = Bar(1) f.a(), f.b(), f.n() (1, 0.0, '') b.a(), b.b(), b.n() (1, 0.0, '') f = Foo(1,1.0) b = Bar(1,1.0) f.a(), f.b(), f.n() (1, 1.0, '') b.a(), b.b(), b.n() (1, 1.0, '') f = Foo(1,1.0,"1") b = Bar(1,1.0,"1") f.a(), f.b(), f.n() (1, 1.0, '1') b.a(), b.b(), b.n() (1, 1.0, '1') f = Foo(a=1) b = Bar(a=1) f.a(), f.b(), f.n() (1, 0.0, '') b.a(), b.b(), b.n() (1, 0.0, '') f = Foo(b=1) b = Bar(b=1) f.a(), f.b(), f.n() (0, 1.0, '') b.a(), b.b(), b.n() (0, 1.0, '') f = Foo(n="1") b = Bar(n="1") f.a(), f.b(), f.n() (0, 0.0, '1') b.a(), b.b(), b.n() (0, 0.0, '1') f = Foo(1,n="1") b = Bar(1,n="1") f.a(), f.b(), f.n() (1, 0.0, '1') b.a(), b.b(), b.n() (1, 0.0, '1') f.set() b.set() f.a(), f.b(), f.n() (0, 0.0, '') b.a(), b.b(), b.n() (0, 0.0, '') f.set(1) b.set(1) f.a(), f.b(), f.n() (1, 0.0, '') b.a(), b.b(), b.n() (1, 0.0, '') f.set(1,1.0) b.set(1,1.0) f.a(), f.b(), f.n() (1, 1.0, '') b.a(), b.b(), b.n() (1, 1.0, '') f.set(1,1.0,"1") b.set(1,1.0,"1") f.a(), f.b(), f.n() (1, 1.0, '1') b.a(), b.b(), b.n() (1, 1.0, '1') f.set(a=1) b.set(a=1) f.a(), f.b(), f.n() (1, 0.0, '') b.a(), b.b(), b.n() (1, 0.0, '') f.set(b=1) b.set(b=1) f.a(), f.b(), f.n() (0, 1.0, '') b.a(), b.b(), b.n() (0, 1.0, '') f.set(n="1") b.set(n="1") f.a(), f.b(), f.n() (0, 0.0, '1') b.a(), b.b(), b.n() (0, 0.0, '1') f.set(1,n="1") b.set(1,n="1") f.a(), f.b(), f.n() (1, 0.0, '1') b.a(), b.b(), b.n() (1, 0.0, '1') '''
############################# # Sample from my computer ############################# #>>> from time import time #>>> def callNtimes(f, n=100000): #... t = time() #... for i in range(0,n): #... f() #... return time()-t #... #>>> callNtimes(f.set) #1.1100000143051147 #>>> callNtimes(b.set) #2.4220000505447388 #>>> callNtimes(lambda: f.set(1)) #1.968000054359436 #>>> callNtimes(lambda: b.set(1)) #2.8289999961853027 #>>> callNtimes(lambda: f.set(1,.1)) #2.3279999494552612 #>>> callNtimes(lambda: b.set(1,.1)) #2.843999981880188 #>>> callNtimes(lambda: f.set(1,.1,'1')) #2.656000018119812 #>>> callNtimes(lambda: b.set(1,.1,'1')) #2.656999945640564 def run(args = None): import sys import doctest if args is not None: sys.argv = args return doctest.testmod(sys.modules.get(__name__)) if __name__ == '__main__': print "running..." import sys sys.exit(run()[0])
[Nikolay, please leave a blank line between quotations for readability. Thanks!] Nikolay Mladenov <nickm@sitius.com> writes:
I've attached test files.
OK, I don't understand this. Isn't the 2nd constructor redundant?
Yes it is but, having it makes certain calls more efficient (it's cheating, in a way ), see the commented section in the py-file, may be def with args can be made so it is really redundant.
OK, I would like to see "irrelevancies" removed from the code for any test case, though... unless you think the efficiency issue is closely related to the keyword support, in which case you'll have to convince me (please).
Why are you using no_init?
For the same reason, not that it makes sence for my first example, but in general, I want my most offsen called overload to be first in the list - so defined last.
That still doesn't explain why you'd want to use no_init. Why not just define the more-important constructor 2nd?
Doesn't this stuff work for regular functions and member functions, too?
Yes it works, see the test.
Fantastic.
...and shouldn't we get rid of the need to write the outer "args(...)"? It is not necessary to have it, you can write (arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) instead of args(arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) but you'll probably want args("a","b","c", arg("n") = std::string())) instead of (arg("a"), arg("b"), arg("c"), arg("n") = std::string()))
Understood... but is it really wise to give people more than One (And Preferably Only One) Obvious Way To Do It? I might be inclined to support only: args("a","b","c") and: (arg("a"), arg("b"), arg("c"), arg("n") = std::string())) Or even drop the 1st and go with (arg("a"), arg("b"), arg("c")) Thoughts?
I suggest you write the documentation which would explain all this,
Sure, but I think I'll wait a bit to see all of your comments:)
OK.
but posting informally is fine if you try to ensure that I don't have to ask you lots more questions in order to understand it ;-)
I hope this all makes sense to you.
It does. In the examples, I'd prefer to see arg("a") = int() , arg("b") = double() , arg("n") = std::string() or arg("a") = 0 , arg("b") = 0.0 , arg("n") = std::string() or arg("a") = 0 , arg("b") = 0.0 , arg("n") = "" instead of arg("a") = (int)0 , arg("b") = (double)0 , arg("n") = std::string() Aside from that and my previous comments, it looks scrumptious! -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams wrote:
OK, I would like to see "irrelevancies" removed from the code for any test case, though... unless you think the efficiency issue is closely related to the keyword support, in which case you'll have to convince me (please).
I am not sure what needs convincing? There is an efficiency issue. Don't you agree? I put those just so to answer you question on why I have redundant constructor. I don't mean to keep them
Why are you using no_init?
For the same reason, not that it makes sence for my first example, but in general, I want my most offsen called overload to be first in the list - so defined last.
That still doesn't explain why you'd want to use no_init. Why not just define the more-important constructor 2nd?
Are you saying that I should put the first constructor as parameter of the class_ constructor? Cause I really prefer the def way for exporting constructors. I was not realizing the fact that class_() implicitly defines default constructor for a long time, and I think that this is somewhat confusing.
Doesn't this stuff work for regular functions and member functions, too?
Yes it works, see the test.
Fantastic.
...and shouldn't we get rid of the need to write the outer "args(...)"? It is not necessary to have it, you can write (arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) instead of args(arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) but you'll probably want args("a","b","c", arg("n") = std::string())) instead of (arg("a"), arg("b"), arg("c"), arg("n") = std::string()))
Understood... but is it really wise to give people more than One (And Preferably Only One) Obvious Way To Do It?
Why not? They are not *too* different, are they?
I might be inclined to support only:
args("a","b","c")
and:
(arg("a"), arg("b"), arg("c"), arg("n") = std::string()))
Or even drop the 1st and go with
(arg("a"), arg("b"), arg("c"))
Thoughts?
Well, originally I thought that args() is already in the library so it should not be removed. So if you insist on being minimal, than probably we should support args("a","b","c") // which is already there arg("d")=1 and at the end: ( args("a","b","c"), arg("d")=1, arg("e")=std::string() ) ( arg("a"), arg("d")=1, arg("e")=std::string() )
Nikolay Mladenov <nickm@sitius.com> writes:
David Abrahams wrote:
OK, I would like to see "irrelevancies" removed from the code for any test case, though... unless you think the efficiency issue is closely related to the keyword support, in which case you'll have to convince me (please).
I am not sure what needs convincing? There is an efficiency issue. Don't you agree?
I suppose so. I meant that if you want to keep those extra constructors in the examples or tests for this feature I want to be convinced that they're relevant. It sounds like you don't want to keep them, though.
I put those just so to answer you question on why I have redundant constructor.
Thanks.
I don't mean to keep them
Why are you using no_init?
For the same reason, not that it makes sence for my first example, but in general, I want my most offsen called overload to be first in the list - so defined last.
That still doesn't explain why you'd want to use no_init. Why not just define the more-important constructor 2nd?
Are you saying that I should put the first constructor as parameter of the class_ constructor?
Well, at least you *could*.
Cause I really prefer the def way for exporting constructors.
I can understand that.
I was not realizing the fact that class_() implicitly defines default constructor for a long time, and I think that this is somewhat confusing.
Understandable. People were even more confused when BPL allowed them to write classes that couldn't be constructed by default. Maybe we should deprecate the def() way and provide a mechanism for chaining init<...>() instances in the class_<...> constructor (e.g. with commas). At least that would focus all attention in the same place.
...and shouldn't we get rid of the need to write the outer "args(...)"? It is not necessary to have it, you can write (arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) instead of args(arg("a") = (int)0, arg("b") = (double)0, arg("n") = std::string())) but you'll probably want args("a","b","c", arg("n") = std::string())) instead of (arg("a"), arg("b"), arg("c"), arg("n") = std::string()))
Understood... but is it really wise to give people more than One (And Preferably Only One) Obvious Way To Do It?
Why not?
It's "unpythonic". Have you tried >>> import this <wink>?
They are not *too* different, are they?
Sometimes things which are almost the same but subtly different are worse than things which are very different. It bothers me to have two things like that whose name differs only in plurality.
I might be inclined to support only:
args("a","b","c")
and:
(arg("a"), arg("b"), arg("c"), arg("n") = std::string()))
Or even drop the 1st and go with
(arg("a"), arg("b"), arg("c"))
Thoughts?
Well, originally I thought that args() is already in the library so it should not be removed.
We could keep it for backward compatibility but deprecate it by removing it from the docs.
So if you insist on being minimal
I don't insist on it, but I think it might be a good idea.
than probably we should support
args("a","b","c") // which is already there
Grr...
arg("d")=1
I like this one.
and at the end:
( args("a","b","c"), arg("d")=1, arg("e")=std::string() )
Grr... What about ( arg("a"),"b", "c", arg("d")=1, arg("e")=std::string() ) Instead?
( arg("a"), arg("d")=1, arg("e")=std::string() )
Fine. -Dave P.S. I'm not terribly convinced of my own position here. Any bit of additional resistance from you and I'll probably cave ;-) -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams wrote:
Nikolay Mladenov <nickm@sitius.com> writes:
David Abrahams wrote:
OK, I would like to see "irrelevancies" removed from the code for any test case, though... unless you think the efficiency issue is closely related to the keyword support, in which case you'll have to convince me (please).
I am not sure what needs convincing? There is an efficiency issue. Don't you agree?
I suppose so. I meant that if you want to keep those extra constructors in the examples or tests for this feature I want to be convinced that they're relevant. It sounds like you don't want to keep them, though.
It is probably good to make clear the advantages and disadvantage of the different ways of defining optional arguments, but I don't insist doing it in the keywords docs and samples. I'll keep those extra constructors in my code though:)
I was not realizing the fact that class_() implicitly defines default constructor for a long time, and I think that this is somewhat confusing.
Understandable. People were even more confused when BPL allowed them to write classes that couldn't be constructed by default.
Maybe we should deprecate the def() way and provide a mechanism for chaining init<...>() instances in the class_<...> constructor (e.g. with commas). At least that would focus all attention in the same place.
I would better like it if def is the only way of defining constructors. Initially I thought that staticmethod should be implemented as policy, but you convinced me into the "python"-ian way: staticmethod is a statement in python. Well so is __init__, and in python it is def'ed as any other method. Why should we explicitly specify that the class is not constructable (with no_init) (the c++ way), instead of explicitly exporting constructor with def (the python way). "There should be one-- and preferably only one --obvious way to do it" ;-) and def should be it for exporting methods:) I have exported a lot non-constructable classes as bases and as collections of staticmethods. And we don't really need the default constructability shortcut, especially now, having pyste.
It's "unpythonic". Have you tried
>>> import this
I see:). Zen is speaking:) It was good reminding it.
What about
( arg("a"),"b", "c", arg("d")=1, arg("e")=std::string() )
I had not thought about that one and I like it. And I'll do it.
Instead?
( arg("a"), arg("d")=1, arg("e")=std::string() )
Fine.
-Dave
P.S. I'm not terribly convinced of my own position here. Any bit of additional resistance from you and I'll probably cave ;-)
I wasn't really resisting. Until you mentioned deprecating def(init<...>()). And if you promise to forget about it, than we can probably say we agree:) Regards, Nikolay
Nikolay Mladenov <nickm@sitius.com> writes:
David Abrahams wrote:
Nikolay Mladenov <nickm@sitius.com> writes:
David Abrahams wrote:
OK, I would like to see "irrelevancies" removed from the code for any test case, though... unless you think the efficiency issue is closely related to the keyword support, in which case you'll have to convince me (please).
I am not sure what needs convincing? There is an efficiency issue. Don't you agree?
I suppose so. I meant that if you want to keep those extra constructors in the examples or tests for this feature I want to be convinced that they're relevant. It sounds like you don't want to keep them, though.
It is probably good to make clear the advantages and disadvantage of the different ways of defining optional arguments
Agreed.
but I don't insist doing it in the keywords docs and samples.
Good. Probably the tutorial should have some info about this.
I'll keep those extra constructors in my code though:)
It's your code!
I was not realizing the fact that class_() implicitly defines default constructor for a long time, and I think that this is somewhat confusing.
Understandable. People were even more confused when BPL allowed them to write classes that couldn't be constructed by default.
Maybe we should deprecate the def() way and provide a mechanism for chaining init<...>() instances in the class_<...> constructor (e.g. with commas). At least that would focus all attention in the same place.
I would better like it if def is the only way of defining constructors. Initially I thought that staticmethod should be implemented as policy, but you convinced me into the "python"-ian way: staticmethod is a statement in python. Well so is __init__, and in python it is def'ed as any other method.
Well, that's a good point.
Why should we explicitly specify that the class is not constructable (with no_init) (the c++ way), instead of explicitly exporting constructor with def (the python way). "There should be one-- and preferably only one --obvious way to do it" ;-) and def should be it for exporting methods:)
I understand the principle, but sometimes you have to make compromises at the language boundary. Besides, not having to explicitly specify default constructors is both the C++ and the Python way. Consider: >>> class Foo: ... pass # no explicit __init__ ... >>> x = Foo() # OK struct Foo {}; // no explicit constructor Foo x; // OK Why should I have to write an explicit __init__ in order to expose Foo to Python?
I have exported a lot non-constructable classes as bases and as collections of staticmethods.
Yes, but at least if the class is abstract you'll get a compile-time error if you don't supply no_init, while if we eliminate the default constructor definition compiling and linking would succeed for default constructible classes, but fail at runtime when you try to instantiate them (unless you remember the init<...>(), which maybe doesn't appear anywhere in the C++ class declaration). I consider failing at compile time to be far superior.
And we don't really need the default constructability shortcut, especially now, having pyste.
A lot of people still don't use Pyste.
It's "unpythonic". Have you tried
>>> import this
I see:). Zen is speaking:) It was good reminding it.
What about
( arg("a"),"b", "c", arg("d")=1, arg("e")=std::string() )
I had not thought about that one and I like it. And I'll do it.
Instead?
( arg("a"), arg("d")=1, arg("e")=std::string() )
Fine.
-Dave
P.S. I'm not terribly convinced of my own position here. Any bit of additional resistance from you and I'll probably cave ;-)
I wasn't really resisting. Until you mentioned deprecating def(init<...>()). And if you promise to forget about it, than we can probably say we agree:)
I guess I'm not feeling quite *that* relaxed yet ;-) -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams wrote:
Maybe we should deprecate the def() way and provide a mechanism for chaining init<...>() instances in the class_<...> constructor (e.g. with commas). At least that would focus all attention in the same place.
I would better like it if def is the only way of defining constructors. Initially I thought that staticmethod should be implemented as policy, but you convinced me into the "python"-ian way: staticmethod is a statement in python. Well so is __init__, and in python it is def'ed as any other method.
Well, that's a good point.
So you agree here.
Why should we explicitly specify that the class is not constructable (with no_init) (the c++ way), instead of explicitly exporting constructor with def (the python way). "There should be one-- and preferably only one --obvious way to do it" ;-) and def should be it for exporting methods:)
I understand the principle, but sometimes you have to make compromises at the language boundary. Besides, not having to explicitly specify default constructors is both the C++ and the Python way. Consider:
>>> class Foo: ... pass # no explicit __init__ ... >>> x = Foo() # OK
struct Foo {}; // no explicit constructor Foo x; // OK
Why should I have to write an explicit __init__ in order to expose Foo to Python?
And I do here.
I have exported a lot non-constructable classes as bases and as collections of staticmethods.
Yes, but at least if the class is abstract you'll get a compile-time error if you don't supply no_init, while if we eliminate the default constructor definition compiling and linking would succeed for default constructible classes, but fail at runtime when you try to instantiate them (unless you remember the init<...>(), which maybe doesn't appear anywhere in the C++ class declaration). I consider failing at compile time to be far superior.
And completely agree here Ok. I see the dilemma. And I'm starting see your point about the comma separated inits, though I wouldn't really enjoy writing them. But this is getting a little bit far from the keywords, and the init problem really got me cycling.
I guess I'm not feeling quite *that* relaxed yet ;-)
So, do you feel we have differences left about the keywords?
Nikolay Mladenov <nickm@sitius.com> writes:
Ok. I see the dilemma. And I'm starting see your point about the comma separated inits, though I wouldn't really enjoy writing them.
But this is getting a little bit far from the keywords, and the init problem really got me cycling.
I went cycling today, too. 40 miles on the tandem with my wife in the Alaska sunshine with views of all three mountain ranges and Mt. McKinley (Denali)!
I guess I'm not feeling quite *that* relaxed yet ;-)
So, do you feel we have differences left about the keywords?
Nope. -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams <dave@boost-consulting.com> wrote:
But this is getting a little bit far from the keywords, and the init problem really got me cycling.
I went cycling today, too. 40 miles on the tandem with my wife in the Alaska sunshine with views of all three mountain ranges and Mt. McKinley (Denali)!
Wow! I can only imagine! -- Joel de Guzman joel at boost-consulting.com http://www.boost-consulting.com http://spirit.sf.net
David Abrahams <dave@boost-consulting.com> writes:
So, do you feel we have differences left about the keywords?
Nope.
Nikolay, do you have a final set of patches for me? Thanks, Dave -- Dave Abrahams Boost Consulting www.boost-consulting.com
Oh, and I meant to explain a bit of how it works. case 1. init<optional<> > and def(*,*, overloads()) define overloaded functions with fixed arity, meaning that m_min_arity == m_max_arity for each of them. When a call is made that has nargs == overload.m_min_arity == overload.m_max_arity, the overload proceeds case 2. When args are used only one overload is created and in general it will have m_min_arity != m_max_arity, based on how many arguments have default values. When a call is made that matches this (m_min_arity,m_max_arity) range then the default args are used to fill in the blanks to m_max_arity, and only if this is possible the overload proceeds.
Nikolay Mladenov <nickm@sitius.com> writes:
Oh, and I meant to explain a bit of how it works.
case 1. init<optional<> > and def(*,*, overloads()) define overloaded functions with fixed arity, meaning that m_min_arity == m_max_arity for each of them. When a call is made that has nargs == overload.m_min_arity == overload.m_max_arity, the overload proceeds
case 2. When args are used only one overload is created and in general it will have m_min_arity != m_max_arity, based on how many arguments have default values. When a call is made that matches this (m_min_arity,m_max_arity) range then the default args are used to fill in the blanks to m_max_arity, and only if this is possible the overload proceeds.
Thanks, sounds perfect. As you could probably tell from working on it, that approach fell right into all the "slots" I'd left in the code for you (well me, really, but I never got around to it -- so thanks!) -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams wrote:
Thanks, sounds perfect. As you could probably tell from working on it, that approach fell right into all the "slots" I'd left in the code for you (well me, really, but I never got around to it -- so thanks!)
I would even say that you didn't leave me any choice:). I was actually following an artical that you had posted long before that. So the interface should match to what you had in mind.
Nikolay, The stuff is checked in. I added your name to the copyright notices and made a few minor changes to the interface. The HTML needed a lot of work; please validate with something like HTMLTidy in the future. Returning 0 from the ResultConverter seemed problematic, so I changed it to return None. Thanks for the contribution! -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (3)
-
David Abrahams -
Joel de Guzman -
Nikolay Mladenov