Самодельный таппер: выглядит так же, но не идентично - PullRequest
3 голосов
/ 22 января 2020

Для моих оптимизаций я хотел бы получить приличную toupper в R cpp. Я очень новичок в C ++, и я знаю, что сделал это:

#include <Rcpp.h>
using namespace Rcpp;

void C_toupper_String(const String& s) {
  for (char *p =(char *)s.get_cstring();*p!=0;p++) *p = toupper(*p);
}
// [[Rcpp::export]]
StringVector C_toupper(StringVector const& vecteur) {
  StringVector res=clone(vecteur);
  for (int i(0); i < res.size(); ++i) {
    C_toupper_String(res[i]);
  }
  return res;
}

/*** R
teststring <- "HeY I arNaud"
C_toupper(teststring)
toupper(teststring)
identical(C_toupper(teststring),toupper(teststring))
*/

Однако, он не работает должным образом.

> C_toupper(teststring)
[1] "HEY I ARNAUD"

> toupper(teststring)
[1] "HEY I ARNAUD"

> identical(C_toupper(teststring),toupper(teststring))
[1] FALSE

В чем проблема? ? Если возможно, я бы не хотел конвертировать String в std::string, потому что я хотел бы понять, что происходит: смысл перехода на C ++ - уметь избегать копий и преобразований.

Спасибо,

Арно

Ответы [ 2 ]

8 голосов
/ 23 января 2020

Вопрос, почему две строки не проверяют identical, трудно объяснить - две строки, безусловно, выглядят одинаково при проверке своих необработанных байтов (через charToRaw), они не несут атрибутов и не иметь набор кодировки. Так что, на самом деле, они должны быть идентичны.

Чтобы разгадать тайну, нам нужно понять, что на самом деле делает ваш код C ++. Более конкретно, что делает приведение C в стиле C_toupper_String. Из-за их опасности, вы должны никогда использовать C стиль приведения . Ваш код сталкивается с проблемами исключительно из-за этого приведения.

Почему? Потому что String::get_cstring возвращает char const*. Вы отбрасываете его на char* и тем самым отбрасываете его const. Это может быть безопасным, но только если базовое хранилище не const. В противном случае это неопределенное поведение (UB) . Эффекты UB трудно предсказать из-за переписывания кода (например, оптимизации). В этом случае кажется, что он создает код, который портит внутреннюю строку строки R.


Вы принципиально не можете изменять Rcpp::String объекты на месте, они не позволяют это , Но если вы просто хотите избежать копирования, тогда ваш код все равно не достигает своей цели, поскольку ваша C_toupper функция явно копирует ввод на первом шаге.

Как сказал Дирк, правильный способ решения этой проблемы - использовать доступный R cpp API. А в случае модификаций строк это означает преобразование вашего ввода в std::string, выполнение изменений, а затем обратное преобразование. Это копирует, но ваш текущий код тоже. Вот один хороший способ написания этого кода:

#include <Rcpp.h>
#include <cctype>
#include <string>

// [[Rcpp::export]]
Rcpp::StringVector C_toupper(Rcpp::StringVector const& vec) {
    std::vector<std::string> res(vec.begin(), vec.end());
    for (std::string& str : res) {
        for (char& c : str) c = std::toupper(c);
    }

    return Rcpp::wrap(res);
}

Обратите внимание, что это будет иногда давать неправильные результаты, потому что std::toupper принципиально не может иметь дело с определенными характеристиками Unicode. R's toupper делает лучше, но также имеет некоторые проблемы. Правильное решение использует пакеты {stringr} или {stringi}.

1 голос
/ 22 января 2020

Почему бы не однострочно использовать библиотеку C ++? Если вы не действительно опытный в C ++, вы вряд ли сможете победить его. Следующий код только с отступом для отображения здесь, это одна строка в моем сеансе R здесь.

R> Rcpp::cppFunction("std::string tU(std::string s) { std::string u(s); \
 for (unsigned int i=0; i<u.length(); i++) u[i] = std::toupper(u[i]); return(u); }")
R> tU("aBcDe")
[1] "ABCDE"
R> 
...