r-lib / cpp11

cpp11 helps you to interact with R objects using C++ code.
https://cpp11.r-lib.org/
Other
199 stars 46 forks source link

initializer_list constructors for cpp11:: types #59

Open romainfrancois opened 4 years ago

romainfrancois commented 4 years ago

Would it be useful to have the initializer lists constructors also for the non writable classes, or should they really only be constructed from SEXP, e.g. I enjoy:

cpp11::cpp_function('cpp11::writable::strings words() {
  return {"person", "man", "woman", "camera", "tv"};
}')
words()
#> [1] "person" "man"    "woman"  "camera" "tv"

but this would be useful too:

cpp11::cpp_function('cpp11::strings words() {
  return {"person", "man", "woman", "camera", "tv"};
}', quiet = FALSE)
#> clang++ -mmacosx-version-min=10.13 -std=gnu++11 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG -I/Users/romainfrancois/.R/library/4.0/cpp11/include  -I/usr/local/include   -fPIC  -Wall -O3 -Wall -Wimplicit-int-float-conversion -c /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmphIuzJv/filebd3a13b05d00/src/code_1.cpp -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmphIuzJv/filebd3a13b05d00/src/code_1.o
#> /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmphIuzJv/filebd3a13b05d00/src/code_1.cpp:6:10: error: no matching constructor for initialization of 'cpp11::strings' (aka 'r_vector<cpp11::r_string>')
#>   return {"person", "man", "woman", "camera", "tv"};
#>          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:61:3: note: candidate constructor not viable: requires 2 arguments, but 5 were provided
#>   r_vector(SEXP data, bool is_altrep);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:59:3: note: candidate constructor not viable: requires single argument 'data', but 5 arguments were provided
#>   r_vector(SEXP data);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:91:3: note: candidate constructor not viable: requires single argument 'rhs', but 5 arguments were provided
#>   r_vector(const r_vector& rhs) {
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:57:3: note: candidate constructor not viable: requires 0 arguments, but 5 were provided
#>   r_vector() = default;
#>   ^
#> 1 error generated.
#> make: *** [/private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmphIuzJv/filebd3a13b05d00/src/code_1.o] Error 1
#> Error in dyn.load(shared_lib, local = TRUE, now = TRUE): unable to load shared object '/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T//RtmphIuzJv/filebd3a13b05d00/src/code_1.so':
#>   dlopen(/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T//RtmphIuzJv/filebd3a13b05d00/src/code_1.so, 6): image not found
words()
#> Error in .Call("_code_1_words", PACKAGE = "code_1"): "_code_1_words" not available for .Call() for package "code_1"

Created on 2020-07-24 by the reprex package (v0.3.0.9001)

jimhester commented 4 years ago

My main hesitation is that currently the read only vector classes never allocate on Rs heap, which seems a nice property when you are reasoning about them.

Really we should annotate the constructors with noexcept to make this more explicit.

I acknowledge it would make this particular case nicer to have them. But I think for the overall consistency it would be better if they were not included.

romainfrancois commented 4 years ago

Perhaps instead this could be a (set of) factory function, similar (in concept to) Vector::create() that Rcpp has:

At the moment we can construct a writable::list of anything that as_cpp<> knows how to handle, but only when named, i.e with the _nm =.

cpp11::cpp_function('sexp named_list(){
  return cpp11::writable::list({
    "a"_nm = 1, "b"_nm = 2.3
  });
}', quiet = FALSE)
#> clang++ -mmacosx-version-min=10.13 -std=gnu++11 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG -I/Users/romainfrancois/.R/library/4.0/cpp11/include  -I/usr/local/include   -fPIC  -Wall -O3 -Wall -Wimplicit-int-float-conversion -c /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a1117ea2a/src/code_0.cpp -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a1117ea2a/src/code_0.o
#> clang++ -mmacosx-version-min=10.13 -std=gnu++11 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG -I/Users/romainfrancois/.R/library/4.0/cpp11/include  -I/usr/local/include   -fPIC  -Wall -O3 -Wall -Wimplicit-int-float-conversion -c /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a1117ea2a/src/cpp11.cpp -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a1117ea2a/src/cpp11.o
#> clang++ -mmacosx-version-min=10.13 -std=gnu++11 -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/usr/local/lib -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a1117ea2a/src/code_0.so /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a1117ea2a/src/code_0.o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a1117ea2a/src/cpp11.o -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
named_list()
#> $a
#> [1] 1
#> 
#> $b
#> [1] 2.3

cpp11::cpp_function('sexp unnamed_list(){
  return cpp11::writable::list({
    1, 2.3
  });
}', quiet = FALSE)
#> clang++ -mmacosx-version-min=10.13 -std=gnu++11 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG -I/Users/romainfrancois/.R/library/4.0/cpp11/include  -I/usr/local/include   -fPIC  -Wall -O3 -Wall -Wimplicit-int-float-conversion -c /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a49ae4763/src/code_1.cpp -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a49ae4763/src/code_1.o
#> /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a49ae4763/src/code_1.cpp:6:10: error: no matching constructor for initialization of 'cpp11::writable::list' (aka 'r_vector<SEXPREC *>')
#>   return cpp11::writable::list({
#>          ^                     ~
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:274:3: note: candidate constructor not viable: cannot convert initializer list argument to 'const SEXP' (aka 'SEXPREC *const')
#>   r_vector(const SEXP& data);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:275:3: note: candidate constructor not viable: cannot convert initializer list argument to 'SEXP' (aka 'SEXPREC *')
#>   r_vector(SEXP&& data);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:280:3: note: candidate constructor not viable: no known conversion from 'int' to 'const char *' for 1st argument
#>   r_vector(std::initializer_list<const char*> il);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:281:3: note: candidate constructor not viable: no known conversion from 'int' to 'std::__1::basic_string<char>' for 1st argument
#>   r_vector(std::initializer_list<std::string> il);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:289:3: note: candidate constructor not viable: cannot convert initializer list argument to 'const R_xlen_t' (aka 'const long')
#>   r_vector(const R_xlen_t size);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:293:3: note: candidate constructor not viable: cannot convert initializer list argument to 'const cpp11::writable::r_vector<SEXPREC *>'
#>   r_vector(const r_vector& rhs);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:294:3: note: candidate constructor not viable: cannot convert initializer list argument to 'cpp11::writable::r_vector<SEXPREC *>'
#>   r_vector(r_vector&& rhs);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:296:3: note: candidate constructor not viable: cannot convert initializer list argument to 'const cpp11::r_vector<SEXPREC *>'
#>   r_vector(const cpp11::r_vector<T>& rhs);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/list.hpp:74:24: note: candidate constructor not viable: no known conversion from 'int' to 'SEXPREC *' for 1st argument
#> inline r_vector<SEXP>::r_vector(std::initializer_list<SEXP> il)
#>                        ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/list.hpp:85:24: note: candidate constructor not viable: no known conversion from 'int' to 'cpp11::named_arg' for 1st argument
#> inline r_vector<SEXP>::r_vector(std::initializer_list<named_arg> il)
#>                        ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:287:3: note: candidate template ignored: couldn't infer template argument 'V'
#>   r_vector(const V& obj);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:284:3: note: candidate constructor template not viable: requires 2 arguments, but 1 was provided
#>   r_vector(Iter first, Iter last);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:273:3: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
#>   r_vector() = default;
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:276:3: note: candidate constructor not viable: requires 2 arguments, but 1 was provided
#>   r_vector(const SEXP& data, bool is_altrep);
#>   ^
#> /Users/romainfrancois/.R/library/4.0/cpp11/include/cpp11/r_vector.hpp:277:3: note: candidate constructor not viable: requires 2 arguments, but 1 was provided
#>   r_vector(SEXP&& data, bool is_altrep);
#>   ^
#> 1 error generated.
#> make: *** [/private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpMgV4YE/file16e1a49ae4763/src/code_1.o] Error 1
#> Error in dyn.load(shared_lib, local = TRUE, now = TRUE): unable to load shared object '/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T//RtmpMgV4YE/file16e1a49ae4763/src/code_1.so':
#>   dlopen(/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T//RtmpMgV4YE/file16e1a49ae4763/src/code_1.so, 6): image not found
unnamed_list()
#> Error in .Call("_code_1_unnamed_list", PACKAGE = "code_1"): "_code_1_unnamed_list" not available for .Call() for package "code_1"

Created on 2020-07-27 by the reprex package (v0.3.0.9001)

Maybe ::c<Args...> so that we could e.g.

list x = list::c(1, 2.3);

or

auto x = c<SEXP>(1, 2.3)
romainfrancois commented 4 years ago

I've been playing with this as a way to get back on track with variadic templates.

Not sure about the syntax, but I like the feature, e.g. this makes list(a = 1L, "test")

auto vec = c<cpp11::list>("a"_nm = 1, "test");
#include "cpp11.hpp"

template <typename... Args>
struct is_named ;

template <typename T, typename... Args>
struct is_named<T, Args...> : public std::integral_constant<
  bool,
  std::is_same<typename std::decay<T>::type, cpp11::named_arg>::value || is_named<Args...>::value
>{};

template <>
struct is_named<> : std::false_type {};

template <typename Vector>
struct c {
  using value_type = typename Vector::value_type;
  using writableVector = cpp11::writable::r_vector<value_type>;

  template <typename... Args>
  c(Args&&... args) : data(sizeof...(Args)) {
    int n = sizeof...(Args);
    fill_values(0, std::forward<Args>(args)...);

    if (is_named<Args...>::value) {
      cpp11::writable::strings names(n);
      fill_names(names, 0, std::forward<Args>(args)...);
      data.names() = names;
    }
  }

  template <>
  c<>() : data((R_xlen_t)0){}

  inline operator SEXP() const {
    return data;
  }

private:

  template <typename T>
  void set(int pos, T&& value, std::false_type) {
    data[pos] = value;
  }

  template <typename T>
  void set(int pos, T&& value, std::true_type) {
    data[pos] = cpp11::as_cpp<value_type>(value.value());
  }

  template <typename T>
  void set_name(cpp11::writable::strings& names, int pos, T&& value, std::false_type) {}

  template <typename T>
  void set_name(cpp11::writable::strings& names, int pos, T&& value, std::true_type) {
    names[pos] = value.name();
  }

  template <typename T, typename... Args>
  void fill_names(cpp11::writable::strings& names, int pos, T&& value, Args&&...args) {
    set_name(names, pos, value, typename std::is_same<typename std::decay<T>::type, cpp11::named_arg>::type());
    fill_names(names, pos + 1, std::forward<Args>(args)...);
  }

  template <typename T>
  void fill_names(cpp11::writable::strings& names, int pos, T&& value) {
    set_name(names, pos, value, typename std::is_same<typename std::decay<T>::type, cpp11::named_arg>::type());
  }

  template <typename T, typename... Args>
  void fill_values(int pos, T&& value, Args&&...args) {
    set(pos, value, typename std::is_same<typename std::decay<T>::type, cpp11::named_arg>::type());
    fill_values(pos + 1, std::forward<Args>(args)...);
  }

  template <typename T>
  void fill_values(int pos, T&& value) {
    set(pos, value, typename std::is_same<typename std::decay<T>::type, cpp11::named_arg>::type());
  }

  writableVector data;
};

template <>
template <typename T>
void c<cpp11::list>::set(int pos, T&& value, std::false_type) {
  data[pos] = cpp11::as_sexp(value);
}

template <>
template <typename T>
void c<cpp11::list>::set(int pos, T&& value, std::true_type) {
  data[pos] = value.value();
}

template <>
template <typename T>
void c<cpp11::strings>::set(int pos, T&& value, std::false_type) {
  data[pos] = cpp11::r_string(value);
}

template <>
template <typename T>
void c<cpp11::strings>::set(int pos, T&& value, std::true_type) {
  data[pos] = cpp11::strings(value.value())[0];
}

using namespace cpp11::literals;

[[cpp11::register]]
void test() {
  {
    Rprintf("unnamed list\n");
    auto vec = c<cpp11::list>(1, "test");
    Rf_PrintValue(vec);
  }

  {
    Rprintf("partially named list\n");
    auto vec = c<cpp11::list>("a"_nm = 1, "test");
    Rf_PrintValue(vec);
  }

  {
    Rprintf("empty list\n");
    auto vec = c<cpp11::list>();
    Rf_PrintValue(vec);
  }

  {
    Rprintf("unnamed integers\n");
    auto vec = c<cpp11::integers>(1, 2);
    Rf_PrintValue(vec);
  }

  {
    Rprintf("partially named integers\n");
    auto vec = c<cpp11::integers>("a"_nm = 1, 2);
    Rf_PrintValue(vec);
  }

  {
    Rprintf("empty integers\n");
    auto vec = c<cpp11::integers>();
    Rf_PrintValue(vec);
  }

  {
    Rprintf("unnamed strings\n");
    auto vec = c<cpp11::strings>("one", "two");
    Rf_PrintValue(vec);
  }

  {
    Rprintf("partially named strings\n");
    auto vec = c<cpp11::strings>("a"_nm = "one", "two");
    Rf_PrintValue(vec);
  }

  {
    Rprintf("empty strings\n");
    auto vec = c<cpp11::strings>();
    Rf_PrintValue(vec);
  }

Because of how cpp11 works this could be easily incubated in another vendorable package

romainfrancois commented 4 years ago
$ Rscript /tmp/test.R
clang++ -mmacosx-version-min=10.13 -std=gnu++11 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG -I/Users/romainfrancois/.R/library/4.0/cpp11/include  -I/usr/local/include   -fPIC  -Wall -O3 -Wall -Wimplicit-int-float-conversion -c /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpLy5ZFe/file17cbe28f12e16/src/test.cpp -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpLy5ZFe/file17cbe28f12e16/src/test.o
clang++ -mmacosx-version-min=10.13 -std=gnu++11 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG -I/Users/romainfrancois/.R/library/4.0/cpp11/include  -I/usr/local/include   -fPIC  -Wall -O3 -Wall -Wimplicit-int-float-conversion -c /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpLy5ZFe/file17cbe28f12e16/src/cpp11.cpp -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpLy5ZFe/file17cbe28f12e16/src/cpp11.o
clang++ -mmacosx-version-min=10.13 -std=gnu++11 -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/usr/local/lib -o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpLy5ZFe/file17cbe28f12e16/src/test.so /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpLy5ZFe/file17cbe28f12e16/src/test.o /private/var/folders/4b/hn4fq98s6810s4ccv2f9hm2h0000gn/T/RtmpLy5ZFe/file17cbe28f12e16/src/cpp11.o -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
unnamed list
[[1]]
[1] 1

[[2]]
[1] "test"

partially named list
$a
[1] 1

[[2]]
[1] "test"

empty list
list()
unnamed integers
[1] 1 2
partially named integers
a   
1 2 
empty integers
integer(0)
unnamed strings
[1] "one" "two"
partially named strings
    a       
"one" "two" 
empty strings
character(0)
jimhester commented 4 years ago

partially named lists are esoteric enough in practice that I am not sure it is worth the added complexity.

But I don't have a very strong opinion in either direction.

romainfrancois commented 4 years ago

Yeah I guess, partially named was a bonus, but the ability to create a list from various types is useful though, and that's not something we can do with initializer lists