clang? gleichzeitig über mehrere Container laufen

-AB-

Well-Known Member
c-b Team
c-b Experte
#1
Moin moin allerseits :)

Hier hat doch sicher jemand clang, und mag mal meinen Code testen? Ich hab gehört, bei variadic templates ist clang da etwas picky, jedenfalls verglichen mit dem GCC.

Gerne lass ich auch mal einen Visual C++ drüber laufen ;)

Kurz eine kleine Vorstellung, was der Code können soll...


Man kennt es ja, jemand dachte sich, besonders schlau zu sein und hat statt eines std::vector<MyStruct> zwei co-indizierte Vektoren angelegt, also: Zwei Container gleicher Länge, deren Objekte mit gleichem Index in irgendeiner Beziehung verwandt sind.

Manchmal bekommt man aus einer API auch bloß Daten auf diese Art heraus (Vertices und Oberflächennormale, IDs und Namen von Objekten, ...), so oder so haben wir jetzt aber folgendes Dilemma:

C++:
auto& myPolygons = myObj.getPolygons();

std::for_each(myPolygons.begin(), myPolygons.end(),
                [](Polygon& poly)
                {
                    // mach was mit dem Polygon.
                    // neu - brauchen jetzt auch die Normals!
                }
            );

auto& myNormals = myObj.getSurfaceNormals(); // ...und jetzt??
Jetzt muss man hingehen, und mühsam eine Schleife schreiben, die über beide Container drüber läuft, die Indices prüft, und den gewünschten Code auf jedes Objekt ausführt.

Herkömmlicher Code würde das jetzt so machen. Advanced future supercode hingegen verwendet eine for_each Funktion, die es erlaubt, über mehrere Container/begin-end-Ranges gleichzeitig zu iterieren:

C++:
auto& myPolygons = myObj.getPolygons();
auto& myNormals = myObj.getSurfaceNormals();

for_each_multiple([](Polygon& ply, Normal& nml)
                {
                     // alles super!
                },
                myPolygons.begin(),
                myPolygons.end(),
                myNormals.begin(),
                myNormals.end()
            );

But wait! There's more!

Dank variadic templates musste ich nur eine einzige Funktion schreiben (naja, eigentlich dann doch drei) für jede beliebige Anzahl von Containern/Iteratoren.

Hier der Code, den ich gerne mal in clang und einem aktuellen VS compiliert hätte...
C++:
namespace detail
{
    struct stop_recursion{};

    template <typename Action, typename... Params>
    bool for_each_multiple_impl(stop_recursion, Action action, Params&&... params)
    {
        action(std::forward<Params>(params)...);
        return true;
    }

    template <typename ForwardIterator, typename... OtherParams>
    bool for_each_multiple_impl(ForwardIterator& first, const ForwardIterator& last, OtherParams&&... params)
    {
        if(first == last)
            return false;

        bool othersReturn = for_each_multiple_impl(std::forward<OtherParams>(params)..., *first);
        ++first;

        return true && othersReturn;
    }
}

///as std::for_each, just that it iterates over multiple containers the same time and calls the action passed with the current element of each container. unfortunately, expects the action first, then the iterators.
template <typename... Iters, typename Action>
Action for_each_multiple(Action action, Iters... iters) //TODO enhance with a recursive decltype-determining function... get the last param of iters, -> this is our return type - then we can rearrange the order and have action last.
{
    //note that iters are taken by copy. we are referencing them from now on, and need a clean copy to work on.
    bool succeeded = true;
    auto recursionStopper = detail::stop_recursion();

    while(succeeded)
        succeeded = detail::for_each_multiple_impl(iters..., recursionStopper, action);

    return std::move(action);
}
Und Code, mit dem man gleich bisschen testen kann:
C++:
auto action1 = [](int& val){std::cout << val << std::endl;};
auto action2 = [](int& val1, int& val2){std::cout << val1 << ", " << val2 << std::endl;};
auto action3 = [](int& val1, int& val2, int& val3){std::cout << val1 << ", " << val2 << ", " << val3 << std::endl;};

std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
std::vector<int> vec3 = {7, 8, 9};

const auto beg1 = vec1.begin();
const auto end1 = vec1.end();
const auto beg2 = vec2.begin();
const auto end2 = vec2.end();
const auto beg3 = vec3.begin();
const auto end3 = vec3.end();

for_each_multiple(action1, beg1, end1);
for_each_multiple(action2, beg1, end1, beg2, end2);
for_each_multiple(action3, beg1, end1, beg2, end2, beg3, end3);
Sollte eine Ausgabe wie folgt produzieren:
Code:
1
2
3
1, 4
2, 5
3, 6
1, 4, 7
2, 5, 8
3, 6, 9

Gedanken? Ideen? Compilerfehler?
 

German

Well-Known Member
c-b Experte
#2
Hallo -AB-

MinGW/GCC tut wie es soll, ebenso VS Express 2015. Beides unter Win10.
Mit clang hab ich nur mal kurz experimentiert (noch unter Win7), aber irgendwie nicht geschafft es in eine IDE einzubinden. Somit kann ich dazu leider nicht sagen, sorry.

EDIT
Falls C-ler unterwegs sind, die clang installiert haben und -AB-s Codeschnipsel nicht zusammenfügen können, hier komplett mit Includes:
C++:
#include <iostream>
#include <vector>
#include <utility>

namespace detail
{
  struct stop_recursion {};

  template <typename Action, typename... Params>
  bool for_each_multiple_impl(stop_recursion, Action action, Params&&... params)
  {
    action(std::forward<Params>(params)...);
    return true;
  }

  template <typename ForwardIterator, typename... OtherParams>
  bool for_each_multiple_impl(ForwardIterator& first, const ForwardIterator& last, OtherParams&&... params)
  {
    if (first == last)
      return false;

    bool othersReturn = for_each_multiple_impl(std::forward<OtherParams>(params)..., *first);
    ++first;

    return true && othersReturn;
  }
}

///as std::for_each, just that it iterates over multiple containers the same time and calls the action passed with the current element of each container. unfortunately, expects the action first, then the iterators.
template <typename... Iters, typename Action>
Action for_each_multiple(Action action, Iters... iters) //TODO enhance with a recursive decltype-determining function... get the last param of iters, -> this is our return type - then we can rearrange the order and have action last.
{
  //note that iters are taken by copy. we are referencing them from now on, and need a clean copy to work on.
  bool succeeded = true;
  auto recursionStopper = detail::stop_recursion();

  while (succeeded)
    succeeded = detail::for_each_multiple_impl(iters..., recursionStopper, action);

  return std::move(action);
}


int main()
{
  auto action1 = [](int& val) {std::cout << val << std::endl; };
  auto action2 = [](int& val1, int& val2) {std::cout << val1 << ", " << val2 << std::endl; };
  auto action3 = [](int& val1, int& val2, int& val3) {std::cout << val1 << ", " << val2 << ", " << val3 << std::endl; };

  std::vector<int> vec1 = { 1, 2, 3 };
  std::vector<int> vec2 = { 4, 5, 6 };
  std::vector<int> vec3 = { 7, 8, 9 };

  const auto beg1 = vec1.begin();
  const auto end1 = vec1.end();
  const auto beg2 = vec2.begin();
  const auto end2 = vec2.end();
  const auto beg3 = vec3.begin();
  const auto end3 = vec3.end();

  for_each_multiple(action1, beg1, end1);
  for_each_multiple(action2, beg1, end1, beg2, end2);
  for_each_multiple(action3, beg1, end1, beg2, end2, beg3, end3);

  return 0;
}
 
Zuletzt bearbeitet:

lano

Well-Known Member
c-b Experte
#3
C-ler ?
Von den mir bekannten hat der Eine die Frage gestellt und der Andere schon geantwortet :)

@-AB- Codeschnipsel mal eben kurz... Das kann immer was werden. Besser nen kleines ... So wie German das gemacht hat ;)

Bitte:
Code:
root@alien:/usr/src/test# clang++ -std=c++14 -pedantic -Wall test.c -o test -v      
Debian clang version 3.5.0-10 (tags/RELEASE_350/final) (based on LLVM 3.5.0)
Target: i386-pc-linux-gnu
Thread model: posix
Found candidate GCC installation: /usr/bin/../lib/gcc/i586-linux-gnu/4.8
Found candidate GCC installation: /usr/bin/../lib/gcc/i586-linux-gnu/4.8.4
Found candidate GCC installation: /usr/bin/../lib/gcc/i586-linux-gnu/4.9
Found candidate GCC installation: /usr/bin/../lib/gcc/i586-linux-gnu/4.9.2
Found candidate GCC installation: /usr/lib/gcc/i586-linux-gnu/4.8
Found candidate GCC installation: /usr/lib/gcc/i586-linux-gnu/4.8.4
Found candidate GCC installation: /usr/lib/gcc/i586-linux-gnu/4.9
Found candidate GCC installation: /usr/lib/gcc/i586-linux-gnu/4.9.2
Selected GCC installation: /usr/bin/../lib/gcc/i586-linux-gnu/4.9
Candidate multilib: .;@m32
Selected multilib: .;@m32
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
"/usr/lib/llvm-3.5/bin/clang" -cc1 -triple i386-pc-linux-gnu -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -main-file-name test.c -mrelocation-model static -mdisable-fp-elim -fmath-errno -masm-verbose -mconstructor-aliases -fuse-init-array -target-cpu pentium4 -target-linker-version 2.25 -v -dwarf-column-info -resource-dir /usr/lib/llvm-3.5/bin/../lib/clang/3.5.0 -internal-isystem /usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/c++/4.9 -internal-isystem /usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/i386-linux-gnu/c++/4.9 -internal-isystem /usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/i386-linux-gnu/c++/4.9 -internal-isystem /usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/c++/4.9/backward -internal-isystem /usr/include/clang/3.5.0/include/ -internal-isystem /usr/local/include -internal-isystem /usr/lib/llvm-3.5/bin/../lib/clang/3.5.0/include -internal-externc-isystem /usr/bin/../lib/gcc/i586-linux-gnu/4.9/include -internal-externc-isystem /usr/include/i386-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wall -pedantic -std=c++14 -fdeprecated-macro -fdebug-compilation-dir /usr/src/test -ferror-limit 19 -fmessage-length 181 -mstackrealign -fobjc-runtime=gcc -fcxx-exceptions -fexceptions -fdiagnostics-show-option -fcolor-diagnostics -o /tmp/test-65409b.o -x c++ test.c
clang -cc1 version 3.5.0 based upon LLVM 3.5.0 default target i386-pc-linux-gnu
ignoring nonexistent directory "/include"
ignoring duplicate directory "/usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/i386-linux-gnu/c++/4.9"
ignoring duplicate directory "/usr/include/clang/3.5.0/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/c++/4.9
/usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/i386-linux-gnu/c++/4.9
/usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../../include/c++/4.9/backward
/usr/include/clang/3.5.0/include
/usr/local/include
/usr/bin/../lib/gcc/i586-linux-gnu/4.9/include
/usr/include/i386-linux-gnu
/usr/include
End of search list.
"/usr/bin/ld" --hash-style=both --build-id --eh-frame-hdr -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../i386-linux-gnu/crt1.o /usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../i386-linux-gnu/crti.o /usr/bin/../lib/gcc/i586-linux-gnu/4.9/crtbegin.o -L/usr/bin/../lib/gcc/i586-linux-gnu/4.9 -L/usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../i386-linux-gnu -L/lib/i386-linux-gnu -L/usr/lib/i386-linux-gnu -L/usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../.. -L/usr/lib/llvm-3.5/bin/../lib -L/lib -L/usr/lib /tmp/test-65409b.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/bin/../lib/gcc/i586-linux-gnu/4.9/crtend.o /usr/bin/../lib/gcc/i586-linux-gnu/4.9/../../../i386-linux-gnu/crtn.o
root@alien:/usr/src/test# ./test
1
2
3
1, 4
2, 5
3, 6
1, 4, 7
2, 5, 8
3, 6, 9
root@alien:/usr/src/test#
 

lano

Well-Known Member
c-b Experte
#4
Code:
root@alien:/usr/src/test# g++ -std=c++11 test.c -o test
root@alien:/usr/src/test# ./test
1
2
3
1, 4
2, 5
3, 6
1, 4, 7
2, 5, 8
3, 6, 9
root@alien:/usr/src/test# g++ --version
g++ (Debian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Falls das auch von Interesse sein sollte
 

-AB-

Well-Known Member
c-b Team
c-b Experte
#5
C-ler ?
Von den mir bekannten hat der Eine die Frage gestellt und der Andere schon geantwortet
Nein nein nein. Ich bin eindeutig kein C-ler. Wenn, dann ein C++er :)
@AGGROStar1991 ist glaub ich hauptsächlich mit clang unterwegs und schwört auf dessen Fehlermeldungen. Und weil ich mindestens 3 Tage lang versucht hab, diese dumme Funktion zu schreiben, und letztendlich festhing, weil ich versucht habe, etwas umzusetzen, was mir GCC zwar hätte durchgehen lassen, clang aber (laut irgendwem im Internet) nicht, dachte ich mir, ich geh mal auf Nummer sicher...

Aber danke euch beiden, dann klappts ja.
Gedacht war der Code eigentlich für eine Ressource, da ist grad eh noch eine pending (geht auch ums iterieren...)
 

German

Well-Known Member
c-b Experte
#6
Du? :) Mir ging es da hauptsächlich um die Header, die ggf. unbekannt für jemand gewesen wären, der sich eher mit C beschäftigt.

@-AB-
Diese stop_recursion Klasse hat mich erst mal schön in's Grübeln versetzt ;) Coole Lösung.
Was mir noch ein bisschen suspekt ist, ist diese "variadic template" Geschichte. Irgendwie musst du sicherstellen, dass du zusammengehörende Iteratoren übergibst.
Sowas wie

for_each_multiple(action1, beg1, end2);

oder

for_each_multiple(action1, beg1, beg2);

rennt erst mal los, ohne dass Compiler oder Debugger meckern. Ich weiß nicht ob dein TODO das abfedert, oder ob du überhaupt vorhast da ranzugehen...
 
Zuletzt bearbeitet:

-AB-

Well-Known Member
c-b Team
c-b Experte
#8
Diese stop_recursion Klasse hat mich erst mal schön in's Grübeln versetzt Coole Lösung.
Mhh, Notlösung. Ich hab es erst ohne versucht - hatte gehofft, da ich ja die Action übergebe könnte er das matchen (in einem Fall Action und "Zeugs dahinter", im anderen Fall "2 Iteratoren und Zeugs") - geht leider aber nicht.

Also so den fixen Punkt erzwungen, an welcher Stelle er die Rekursion abbrechen, und das "Zeugs" als Parameter für die Action werten soll. In C++17 sollte es auch soweit ich mich erinnere eine Art std::is_callable geben, was true für einen Funktor oder Funktionspointer übergibt; damit wäre das Problem dann auch gelöst.

Ich weiß nicht ob dein TODO das abfedert, oder ob du überhaupt vorhast da ranzugehen...
Also, bei deinem Beispiel kann man leider nicht viel machen. Aber auch ein normales std::for_each mit zwei Iteratoren von unterschiedlichen Containern produziert einfach undefiniertes Verhalten...

Man könnte am Anfang die Anzahl der Parameter testen, ob sie ungerade ist, bzw: generell an den Fehlermeldungen arbeiten. Das TODO ist nur, um die Reihenfolge der Parameter zu ändern - std::for_each will erst die Parameter, dann die Action - wenn ich das als Parameter Pack reingebe, hab ich aber nicht den Typen des letzten Elements. Da müsste man aber ne relativ einfache Rekursion für schreiben können, die über das Pack drüber läuft und den letzten Typen returned. (Ich brauch den Typen für den Rückgabewert, da ja eine Action zurückgegeben wird... Die STL machts so, also mach ich das auch.)

Eine Fehlermeldung gibts momentan leider auch nur irgendwo tief genestet, "kann keinen passenden Overload finden" - wenn man z.B. einen Parameter vergisst. Mit einem Check, dass eine ungerade Anzahl Parameter (also gerade Anzahl Iteratoren + Funktion) übergeben wurden, ist die Sache vielleicht etwas klarer. Immerhin *müssen* beides Iteratoren sein, sonst hängts schon am std::enable_if :)
 

German

Well-Known Member
c-b Experte
#9
Nur weil ich nen HalloWelt fehlerfrei zusammen bekomme
Ein bisschen mehr war es schon, soweit ich mich erinnere :)

Also, bei deinem Beispiel kann man leider nicht viel machen. Aber auch ein normales std::for_each mit zwei Iteratoren von unterschiedlichen Containern produziert einfach undefiniertes Verhalten...
OK, das Beispiel war zwar einfach aber nicht besonders sinnreich. Klar, wenn du nur ein Paar begin und end Iteratoren hast, ist die Gefahr (ebenso wie bei std::for_each) nicht besonders groß. Bei zwei Paaren könnte schon jemand auf andere Ideen, wie
for_each_multiple(action2, beg1, beg2, end1, end2);
kommen. Mit vielen Argumenten wird das schnell unübersichtlich. Auf Anhieb wäre mir eingefallen, die Container als Referenz zu übergeben, statt die Iteratoren. Allerdings hat das auch ohne zu Ende gedacht zu haben den Nachteil, dass du dich um die Möglichkeit beschneidest, nur über Teilbereiche der Container zu iterieren :confused: Also wohl keine Überlegung wert ...
 
Oben