Skip to content

Conversation

romainfrancois
Copy link
Contributor

changes in Rcpp modules to allow use of the copy constructor of a class

    #include <Rcpp.h>
    using namespace Rcpp ;

    class Foo{
      public:
        Foo( int x_ ) : x(x_){}
        int x ;
    } ;

    RCPP_MODULE(test){
      class_<Foo>("Foo")
        .constructor<int>()
        .field( "x", &Foo::x)
      ;
  }

This is associated with the copy R function :

f <- new( Foo, 1 )
g <- copy( f)
g$x
!identical( g$.pointer, f$.pointer )

…of a class

with the `copy_constructor` declaration:

```
    #include <Rcpp.h>
    using namespace Rcpp ;

    class Foo{
      public:
        Foo( int x_ ) : x(x_){}
        int x ;
    } ;

    RCPP_MODULE(test){
      class_<Foo>("Foo")
        .constructor<int>()
        .copy_constructor()
        .field( "x", &Foo::x)
      ;
  }
```

This is associated with the `copy` R function :

```
f <- new( Foo, 1 )
g <- copy( f)
g$x
!identical( g$.pointer, f$.pointer )
```
@romainfrancois
Copy link
Contributor Author

One possible (trivial) enhancement would be to use the std::is_copy_constructible trait to automatically detect if there is a copy constructor available instead of having to declare it manually.

@romainfrancois romainfrancois changed the title changes in Rcpp modules to allow declaration of the copy constructor … Module enhancements Mar 31, 2016
@eddelbuettel
Copy link
Member

Looks good from a very first glance (provided of course we get it to pass Travis).

I had called what I put through a full rev.dep 0.12.4.2 but had not committed that yet so we may as well got to 0.12.4.3.

Once minor concern is copy() which is a pretty generic name. I am wondering if we should we be more protectice and call it moduleCopy() or something like that? Or not export it and rely on Rcpp:::copy()?

@eddelbuettel
Copy link
Member

From the raw log:

--------------------------- 
Test file: /home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/unitTests/runit.Module.R 
.setUp (before test.Module): ERROR !! 
Error executing .setUp before test.Module : Error in sourceCpp(file.path(pathToRcppTests, "cpp", file)) : 
  Error 1 occurred building shared library.
.setUp (before test.Module.Constructor): ERROR !! 
Error executing .setUp before test.Module.Constructor : Error in sourceCpp(file.path(pathToRcppTests, "cpp", file)) : 
  Error 1 occurred building shared library.
.setUp (before test.Module.copy.constructor): ERROR !! 
Error executing .setUp before test.Module.copy.constructor : Error in sourceCpp(file.path(pathToRcppTests, "cpp", file)) : 
  Error 1 occurred building shared library.
.setUp (before test.Module.exposed.class): ERROR !! 
Error executing .setUp before test.Module.exposed.class : Error in sourceCpp(file.path(pathToRcppTests, "cpp", file)) : 
  Error 1 occurred building shared library.
.setUp (before test.Module.flexible.semantics): ERROR !! 
Error executing .setUp before test.Module.flexible.semantics : Error in sourceCpp(file.path(pathToRcppTests, "cpp", file)) : 
  Error 1 occurred building shared library.
.setUp (before test.Module.member): ERROR !! 
Error executing .setUp before test.Module.member : Error in sourceCpp(file.path(pathToRcppTests, "cpp", file)) : 
  Error 1 occurred building shared library.
.setUp (before test.Module.property): ERROR !! 
Error executing .setUp before test.Module.property : Error in sourceCpp(file.path(pathToRcppTests, "cpp", file)) : 
  Error 1 occurred building shared library.
--------------------------- 

I presume this builds on your OS X box?

@romainfrancois
Copy link
Contributor Author

About passing tests. I'm working on it.

About naming, not a big issue, I just want/need the functionality, so you can decide whatever name suits you best. I find copy both elegant and to the point of what this actually does. It's generic sure, but that's what namespaces are about.

@eddelbuettel
Copy link
Member

I'm sure you'll have it working in no time. Happy to merge then.

@jjallaire @kevinushey @thirdwing I'd love to hear additional views on naming of copy() and possible collusion. I think we are all aware of how unpleasant such clashes are after the fact. I see six hits for copy at http://rdocumentation.org -- at least no R Base functions would be shadowed. I think I still lean towards a more defensive name.

constructor is now automatic with the `has_copy_constructor` trait.
@romainfrancois
Copy link
Contributor Author

Now dropped the explicit declaration of the copy constructor, we can do this automatically with the Rcpp::traits::has_copy_constructor trait (or presumably better with std::is_copy_constructible` but this is only available in not antique C++ versions).

@romainfrancois
Copy link
Contributor Author

Hmm. My SFINAE trick :

amespace Rcpp{
namespace traits{

  template<typename T>
  class _has_copy_constructor_helper : __sfinae_types {
      template<typename U> struct _Wrap_type { };

      template<typename U>
        static __one __test(U);

      template<typename U>
        static __two __test(...);

    public:
      static const bool value = sizeof(__test<T>(0)) == 1;
    };

  template<typename T> struct has_copy_constructor :
    integral_constant<bool,_has_copy_constructor_helper<T>::value> { };

} // traits
} // Rcpp

does not seem to work on the compiler used in travis :

/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/traits/has_copy_constructor.h: In instantiation of ‘const bool Rcpp::traits::_has_copy_constructor_helper<ModuleTest>::value’:
/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/traits/has_copy_constructor.h:40:31:   instantiated from ‘Rcpp::traits::has_copy_constructor<ModuleTest>���
/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/module/class.h:169:132:   instantiated from ‘SEXPREC* Rcpp::class_<Class>::invoke_copy_constructor(SEXP) [with Class = ModuleTest, SEXP = SEXPREC*]’
Module.cpp:276:1:   instantiated from here
Module.cpp:129:5: error: ‘ModuleTest::ModuleTest(const ModuleTest&)’ is private
/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/traits/has_copy_constructor.h:37:57: error: within this context
/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/traits/has_copy_constructor.h:31:22: error:   initializing argument 1 of ‘static Rcpp::traits::__sfinae_types::__one Rcpp::traits::_has_copy_constructor_helper<T>::__test(U) [with U = ModuleTest, T = ModuleTest, Rcpp::traits::__sfinae_types::__one = char]’
In file included from /home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/Module.h:340:0,
                 from /home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp.h:64,
                 from Module.cpp:23:
Module.cpp: In static member function ‘static Class* Rcpp::CopyConstructor<Class, ok>::get(Class*) [with Class = ModuleTest, bool ok = true]’:
/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/module/class.h:169:132:   instantiated from ‘SEXPREC* Rcpp::class_<Class>::invoke_copy_constructor(SEXP) [with Class = ModuleTest, SEXP = SEXPREC*]’
Module.cpp:276:1:   instantiated from here
Module.cpp:129:5: error: ‘ModuleTest::ModuleTest(const ModuleTest&)’ is private

@kevinushey
Copy link
Contributor

I'm fine with calling the function copy() and instructing users to use Rcpp::copy() if they need to avoid collisions. (The other package that comes to mind that exports a function called copy() is data.table).

Alternatively, copy() could be an S3 / S4 generic and we could have a copy() method for these objects.

Or just call it clone().

@eddelbuettel
Copy link
Member

Oh, I forgot about data.table::copy(). That would be enough of a show-stopper for me then.

I thought of clone() too but we also use that for deep copy at the C++. Too confusing?

@kevinushey
Copy link
Contributor

What about making copy() a method on the S4 class / object? I mean, so you can just write x$copy() (or x@copy()) instead of copy(x).

@romainfrancois
Copy link
Contributor Author

I don't mind at all about the naming. I just put in what made sense to me and I knew this would happen.

@kevinushey you mean something like :

x <- new( Foo, 1)
y <- x$copy()

That's definitely possible, I find this less visually pleasing, and also it means that the class can't have a genuine copy method. Too bad as it is quite a generic name 🤓

Anyway, whatever you decide.

We might have the same argument later on when I introduce something to explicitly destruct (i.e. call the destructor) an object. I was thinking of destruct.

@kevinushey do you have a suggestion for detecting if a class is copy constructible without getting all these compiler errors ?

@kevinushey
Copy link
Contributor

Not really :/ I can just share what we do in RStudio -- it's similar except we don't inherit from a base SFINAE class:

#define RS_GENERATE_HAS_TYPE_TRAIT(__NAME__)                                   \
   template <typename T> struct has_##__NAME__##_impl                          \
   {                                                                           \
      template <typename U, typename V> struct SFINAE                          \
      {                                                                        \
      };                                                                       \
                                                                               \
      template <typename U>                                                    \
      static char test(SFINAE<U, typename U::__NAME__>*);                      \
                                                                               \
      template <typename U> static int test(...);                              \
                                                                               \
      static const bool value = sizeof(test<T>(0)) == sizeof(char);            \
   };                                                                          \
                                                                               \
   template <typename T>                                                       \
   struct has_##__NAME__                                                       \
       : public boost::integral_constant<bool,                                 \
                                         has_##__NAME__##_impl<T>::value>      \
   {                                                                           \
   }

// metafunction for checking if class has 'key_type' type
RS_GENERATE_HAS_TYPE_TRAIT(key_type);

@romainfrancois
Copy link
Contributor Author

yeah that's what we use for ::iterator in has_iterator for example.
Looks like this is not a new problem, http://www.boost.org/doc/libs/1_60_0_b1/libs/type_traits/doc/html/boost_typetraits/reference/is_copy_constructible.html

Note that it fails because of this class in test:

class ModuleTest {
public:
    double value;
    ModuleTest(double v) : value(v) {}
private:
    // hiding those on purpose
    // we work by reference or pointers here. Not by copy.
    ModuleTest(const ModuleTest& other);
    ModuleTest& operator=(const ModuleTest&);
};

Perhaps we could require classes to be copy constructible and hence drop the test. This test is not really relevant.

@romainfrancois
Copy link
Contributor Author

I'm trying a different approach.

#if defined(RCPP_USING_CXX11)
  template <typename T> struct has_copy_constructor :
    integral_constant<bool, std::is_copy_constructible<T>::value >{} ;
#else
  template<typename T> struct has_copy_constructor : true_type {} ;
#endif

So essentially when using C++11, we use the std:: is_copy_constructible meta function to figure it out, and otherwise we just assume that the class can be copy constructed. And when it is not (which is a rare case, we can just disable the feature like this:

namespace Rcpp {
namespace traits {
  template <> struct has_copy_constructor<ModuleTest> : false_type {} ;
}
}

or using the RCPP_DISABLE_COPY_CONSTRUCTOR macro.

RCPP_DISABLE_COPY_CONSTRUCTOR(ModuleTest)

I would have preferred a way without this extra declarative step but this is I think a good compromise, esp given that most of the classes exposable by modules will have some copy constructor, so this is a small price to pay for having the copy feature.

  - explicitely destruct a C++ object (without waiting for the GC)
  - check if an object has been destructed
@romainfrancois
Copy link
Contributor Author

Also adding destruct and is_destructed to this PR.

#include <Rcpp.h>
using namespace Rcpp ;

class Foo{
  public:
    Foo( int x_ ) : x(x_){}
    ~Foo(){
      Rprintf( "~Foo\n" ) ;
    }
    int x ;
} ;

RCPP_MODULE(test){
  class_<Foo>("Foo")
    .constructor<int>()
    .field( "x", &Foo::x)
  ;

}
> foo <- new(Foo, 1)

> foo
C++ object <0x7fd353c910e0> of class 'Foo' <0x7fd353c14600>

> destruct(foo)
~Foo

> foo$.pointer
<pointer: 0x0>

> is_destructed(foo)
[1] TRUE

@eddelbuettel
Copy link
Member

Looks good.

I think I would like copyModule() better than copy(). Anybody have any views on Rcpp::copy() clashing with data.table::copy() and eg rpg::copy() and the few others? Namespaces help, but people still do library() on these packages in interactive work.

Pinging @jjallaire @kevinushey @thirdwing

@jjallaire
Copy link
Member

I don't mind Rcpp::copy so much because I don't think there is much if any
library(Rcpp) going on. If we document it with the qualified syntax then
most people will do it that way anyway.

On Fri, Apr 1, 2016 at 7:56 AM, Dirk Eddelbuettel notifications@github.com
wrote:

Looks good.

I think I would like copyModule() better than copy(). Anybody have any
views on Rcpp::copy() clashing with data.table::copy() and eg rpg::copy()
and the few others? Namespaces help, but people still do library() on
these packages in interactive work.

Pinging @jjallaire https://github.com/jjallaire @kevinushey
https://github.com/kevinushey @thirdwing https://github.com/thirdwing


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#454 (comment)

@eddelbuettel
Copy link
Member

But if we export it in NAMESPACE. And I for one always do library(Rcpp) when I want Rcpp Attributes (rather than prefixing). I also do library(data.table) and there is my clash. I don't much like the idea of these two fighting (and I think data.table::copy() was a less than ideal idea too but that is water under te bridge).

@jjallaire
Copy link
Member

Okay, I get that. I wasn't thinking about calling sourceCpp in an
interactive session.

Rcpp::copyObject

Yuk, but clear and avoids the clash :-\

On Fri, Apr 1, 2016 at 8:18 AM, Dirk Eddelbuettel notifications@github.com
wrote:

But if we export it in NAMESPACE. And I for always do library(Rcpp) when
I want Rcpp Attributes (rather than prefixing). I also do
library(data.table) and there is my clash. I don't much like the idea of
these two fighting (and I think data.table::copy() was a less than ideal
idea too but that is water under te bridge).


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#454 (comment)

@eddelbuettel
Copy link
Member

copyObject works for me, copyModule would work too. destruct seems safe, we could rename for symmetry.

I'll probably deal with all that tomorrow morning. We can let it simmer til then.

@romainfrancois
Copy link
Contributor Author

I can make the changes in the PR once you reach a decision.

copyObject makes more sense to me than copyModule. Clearer what it means.

Romain

Le 1 avr. 2016 à 14:33, Dirk Eddelbuettel notifications@github.com a écrit :

copyObject works for me, copyModule would work too. destruct seems safe, we could rename for symmetry.

I'll probably deal with all that tomorrow morning. We can let it simmer til then.


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub

@eddelbuettel
Copy link
Member

Yes, please change from copy() to copyObject() to be more defensive.

@eddelbuettel eddelbuettel merged commit 43831ae into RcppCore:master Apr 2, 2016
eddelbuettel added a commit that referenced this pull request Apr 2, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants