cpp11
cpp11 copied to clipboard
initializer_list constructors for cpp11:: types
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)
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.
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)
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
$ 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)
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.
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